diff --git a/.gitignore b/.gitignore index be4473eb7b..c5cc09c2a7 100644 --- a/.gitignore +++ b/.gitignore @@ -193,3 +193,4 @@ docs/**/js-api/ # csharp emitter !packages/http-client-csharp/package-lock.json +packages/http-client-csharp/generator/artifacts/ diff --git a/eng/common/scripts/Analyze-Changes.Tests.ps1 b/eng/common/scripts/Analyze-Changes.Tests.ps1 index b38ba9ecbc..7ea65ef7ca 100644 --- a/eng/common/scripts/Analyze-Changes.Tests.ps1 +++ b/eng/common/scripts/Analyze-Changes.Tests.ps1 @@ -10,27 +10,51 @@ Describe 'Analyze-Changes' { } It 'Should return package variables if package specific changes are detected' { - $variables = Get-ActiveVariables @( + $actual = Get-ActiveVariables @( "packages/http-client-csharp/src/constants.ts" ) - $variables | Should -Be 'RunCSharp' + $expected = @('RunCSharp') + + $actual | ForEach-Object { + $_ | Should -BeIn $expected + } } It 'Should return RunCore if common files are changed' { - $variables = Get-ActiveVariables @( + $actual = Get-ActiveVariables @( "packages/compiler/package.json" ) - $variables | Should -Be 'RunCore' + $expected = @('RunCore') + + $actual | ForEach-Object { + $_ | Should -BeIn $expected + } } It 'Should return a combination of core and isolated packages' { - $variables = Get-ActiveVariables @( + $actual = Get-ActiveVariables @( "packages/http-client-csharp/src/constants.ts", "packages/compiler/package.json" ) - $variables | Should -Be 'RunCore', 'RunCSharp' + $expected = @('RunCore', 'RunCSharp') + + $actual | ForEach-Object { + $_ | Should -BeIn $expected + } + } + + It 'Should return RunCSharp and RunCore if .editorconfig is changed' { + $actual = Get-ActiveVariables @( + ".editorconfig" + ) + + $expected = @('RunCore', 'RunCSharp') + + $actual | ForEach-Object { + $_ | Should -BeIn $expected + } } } diff --git a/eng/common/scripts/Analyze-Changes.ps1 b/eng/common/scripts/Analyze-Changes.ps1 index d30ccf01ff..ae9417d653 100644 --- a/eng/common/scripts/Analyze-Changes.ps1 +++ b/eng/common/scripts/Analyze-Changes.ps1 @@ -6,12 +6,12 @@ param( # Represents an isolated package which has its own stages in typespec - ci pipeline class IsolatedPackage { - [string] $Path + [string[]] $Paths [string] $RunVariable [bool] $RunValue - IsolatedPackage([string]$path, [string]$runVariable, [bool]$runValue) { - $this.Path = $path + IsolatedPackage([string[]]$paths, [string]$runVariable, [bool]$runValue) { + $this.Paths = $paths $this.RunVariable = $runVariable $this.RunValue = $runValue } @@ -19,10 +19,10 @@ class IsolatedPackage { # Emitter packages in the repo $isolatedPackages = @{ - "http-client-csharp" = [IsolatedPackage]::new("packages/http-client-csharp", "RunCSharp", $false) - "http-client-java" = [IsolatedPackage]::new("packages/http-client-java", "RunJava", $false) - "http-client-typescript" = [IsolatedPackage]::new("packages/http-client-typescript", "RunTypeScript", $false) - "http-client-python" = [IsolatedPackage]::new("packages/http-client-python", "RunPython", $false) + "http-client-csharp" = [IsolatedPackage]::new(@("packages/http-client-csharp", ".editorconfig"), "RunCSharp", $false) + "http-client-java" = [IsolatedPackage]::new(@("packages/http-client-java"), "RunJava", $false) + "http-client-typescript" = [IsolatedPackage]::new(@("packages/http-client-typescript"), "RunTypeScript", $false) + "http-client-python" = [IsolatedPackage]::new(@("packages/http-client-python"), "RunPython", $false) } # A tree representation of a set of files @@ -125,7 +125,12 @@ function Get-ActiveVariables($changes) { if (-not $runIsolated) { # set each isolated package flag foreach ($package in $isolatedPackages.Values) { - $package.RunValue = $root.PathExists($package.Path) + foreach ($path in $package.Paths) { + $package.RunValue = $package.RunValue -or $root.PathExists($path) + if ($package.RunValue) { + break + } + } } } diff --git a/packages/http-client-csharp/readme.md b/packages/http-client-csharp/emitter/readme.md similarity index 100% rename from packages/http-client-csharp/readme.md rename to packages/http-client-csharp/emitter/readme.md diff --git a/packages/http-client-csharp/src/constants.ts b/packages/http-client-csharp/emitter/src/constants.ts similarity index 100% rename from packages/http-client-csharp/src/constants.ts rename to packages/http-client-csharp/emitter/src/constants.ts diff --git a/packages/http-client-csharp/src/emitter.ts b/packages/http-client-csharp/emitter/src/emitter.ts similarity index 100% rename from packages/http-client-csharp/src/emitter.ts rename to packages/http-client-csharp/emitter/src/emitter.ts diff --git a/packages/http-client-csharp/src/index.ts b/packages/http-client-csharp/emitter/src/index.ts similarity index 100% rename from packages/http-client-csharp/src/index.ts rename to packages/http-client-csharp/emitter/src/index.ts diff --git a/packages/http-client-csharp/src/lib/client-model-builder.ts b/packages/http-client-csharp/emitter/src/lib/client-model-builder.ts similarity index 100% rename from packages/http-client-csharp/src/lib/client-model-builder.ts rename to packages/http-client-csharp/emitter/src/lib/client-model-builder.ts diff --git a/packages/http-client-csharp/src/lib/decorators.ts b/packages/http-client-csharp/emitter/src/lib/decorators.ts similarity index 100% rename from packages/http-client-csharp/src/lib/decorators.ts rename to packages/http-client-csharp/emitter/src/lib/decorators.ts diff --git a/packages/http-client-csharp/src/lib/logger.ts b/packages/http-client-csharp/emitter/src/lib/logger.ts similarity index 100% rename from packages/http-client-csharp/src/lib/logger.ts rename to packages/http-client-csharp/emitter/src/lib/logger.ts diff --git a/packages/http-client-csharp/src/lib/model.ts b/packages/http-client-csharp/emitter/src/lib/model.ts similarity index 100% rename from packages/http-client-csharp/src/lib/model.ts rename to packages/http-client-csharp/emitter/src/lib/model.ts diff --git a/packages/http-client-csharp/src/lib/operation.ts b/packages/http-client-csharp/emitter/src/lib/operation.ts similarity index 100% rename from packages/http-client-csharp/src/lib/operation.ts rename to packages/http-client-csharp/emitter/src/lib/operation.ts diff --git a/packages/http-client-csharp/src/lib/service-authentication.ts b/packages/http-client-csharp/emitter/src/lib/service-authentication.ts similarity index 100% rename from packages/http-client-csharp/src/lib/service-authentication.ts rename to packages/http-client-csharp/emitter/src/lib/service-authentication.ts diff --git a/packages/http-client-csharp/src/lib/typespec-server.ts b/packages/http-client-csharp/emitter/src/lib/typespec-server.ts similarity index 100% rename from packages/http-client-csharp/src/lib/typespec-server.ts rename to packages/http-client-csharp/emitter/src/lib/typespec-server.ts diff --git a/packages/http-client-csharp/src/lib/utils.ts b/packages/http-client-csharp/emitter/src/lib/utils.ts similarity index 100% rename from packages/http-client-csharp/src/lib/utils.ts rename to packages/http-client-csharp/emitter/src/lib/utils.ts diff --git a/packages/http-client-csharp/src/options.ts b/packages/http-client-csharp/emitter/src/options.ts similarity index 100% rename from packages/http-client-csharp/src/options.ts rename to packages/http-client-csharp/emitter/src/options.ts diff --git a/packages/http-client-csharp/src/type/body-media-type.ts b/packages/http-client-csharp/emitter/src/type/body-media-type.ts similarity index 100% rename from packages/http-client-csharp/src/type/body-media-type.ts rename to packages/http-client-csharp/emitter/src/type/body-media-type.ts diff --git a/packages/http-client-csharp/src/type/client-kind.ts b/packages/http-client-csharp/emitter/src/type/client-kind.ts similarity index 100% rename from packages/http-client-csharp/src/type/client-kind.ts rename to packages/http-client-csharp/emitter/src/type/client-kind.ts diff --git a/packages/http-client-csharp/src/type/code-model.ts b/packages/http-client-csharp/emitter/src/type/code-model.ts similarity index 100% rename from packages/http-client-csharp/src/type/code-model.ts rename to packages/http-client-csharp/emitter/src/type/code-model.ts diff --git a/packages/http-client-csharp/src/type/collection-format.ts b/packages/http-client-csharp/emitter/src/type/collection-format.ts similarity index 100% rename from packages/http-client-csharp/src/type/collection-format.ts rename to packages/http-client-csharp/emitter/src/type/collection-format.ts diff --git a/packages/http-client-csharp/src/type/configuration.ts b/packages/http-client-csharp/emitter/src/type/configuration.ts similarity index 100% rename from packages/http-client-csharp/src/type/configuration.ts rename to packages/http-client-csharp/emitter/src/type/configuration.ts diff --git a/packages/http-client-csharp/src/type/converter.ts b/packages/http-client-csharp/emitter/src/type/converter.ts similarity index 100% rename from packages/http-client-csharp/src/type/converter.ts rename to packages/http-client-csharp/emitter/src/type/converter.ts diff --git a/packages/http-client-csharp/src/type/external-docs.ts b/packages/http-client-csharp/emitter/src/type/external-docs.ts similarity index 100% rename from packages/http-client-csharp/src/type/external-docs.ts rename to packages/http-client-csharp/emitter/src/type/external-docs.ts diff --git a/packages/http-client-csharp/src/type/formatted-type.ts b/packages/http-client-csharp/emitter/src/type/formatted-type.ts similarity index 100% rename from packages/http-client-csharp/src/type/formatted-type.ts rename to packages/http-client-csharp/emitter/src/type/formatted-type.ts diff --git a/packages/http-client-csharp/src/type/http-response-header.ts b/packages/http-client-csharp/emitter/src/type/http-response-header.ts similarity index 100% rename from packages/http-client-csharp/src/type/http-response-header.ts rename to packages/http-client-csharp/emitter/src/type/http-response-header.ts diff --git a/packages/http-client-csharp/src/type/input-api-key-auth.ts b/packages/http-client-csharp/emitter/src/type/input-api-key-auth.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-api-key-auth.ts rename to packages/http-client-csharp/emitter/src/type/input-api-key-auth.ts diff --git a/packages/http-client-csharp/src/type/input-auth.ts b/packages/http-client-csharp/emitter/src/type/input-auth.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-auth.ts rename to packages/http-client-csharp/emitter/src/type/input-auth.ts diff --git a/packages/http-client-csharp/src/type/input-client.ts b/packages/http-client-csharp/emitter/src/type/input-client.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-client.ts rename to packages/http-client-csharp/emitter/src/type/input-client.ts diff --git a/packages/http-client-csharp/src/type/input-constant.ts b/packages/http-client-csharp/emitter/src/type/input-constant.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-constant.ts rename to packages/http-client-csharp/emitter/src/type/input-constant.ts diff --git a/packages/http-client-csharp/src/type/input-enum-type-value.ts b/packages/http-client-csharp/emitter/src/type/input-enum-type-value.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-enum-type-value.ts rename to packages/http-client-csharp/emitter/src/type/input-enum-type-value.ts diff --git a/packages/http-client-csharp/src/type/input-intrinsic-type-kind.ts b/packages/http-client-csharp/emitter/src/type/input-intrinsic-type-kind.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-intrinsic-type-kind.ts rename to packages/http-client-csharp/emitter/src/type/input-intrinsic-type-kind.ts diff --git a/packages/http-client-csharp/src/type/input-model-property.ts b/packages/http-client-csharp/emitter/src/type/input-model-property.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-model-property.ts rename to packages/http-client-csharp/emitter/src/type/input-model-property.ts diff --git a/packages/http-client-csharp/src/type/input-oauth2-auth.ts b/packages/http-client-csharp/emitter/src/type/input-oauth2-auth.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-oauth2-auth.ts rename to packages/http-client-csharp/emitter/src/type/input-oauth2-auth.ts diff --git a/packages/http-client-csharp/src/type/input-operation-parameter-kind.ts b/packages/http-client-csharp/emitter/src/type/input-operation-parameter-kind.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-operation-parameter-kind.ts rename to packages/http-client-csharp/emitter/src/type/input-operation-parameter-kind.ts diff --git a/packages/http-client-csharp/src/type/input-operation.ts b/packages/http-client-csharp/emitter/src/type/input-operation.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-operation.ts rename to packages/http-client-csharp/emitter/src/type/input-operation.ts diff --git a/packages/http-client-csharp/src/type/input-parameter.ts b/packages/http-client-csharp/emitter/src/type/input-parameter.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-parameter.ts rename to packages/http-client-csharp/emitter/src/type/input-parameter.ts diff --git a/packages/http-client-csharp/src/type/input-primitive-type-kind.ts b/packages/http-client-csharp/emitter/src/type/input-primitive-type-kind.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-primitive-type-kind.ts rename to packages/http-client-csharp/emitter/src/type/input-primitive-type-kind.ts diff --git a/packages/http-client-csharp/src/type/input-type-kind.ts b/packages/http-client-csharp/emitter/src/type/input-type-kind.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-type-kind.ts rename to packages/http-client-csharp/emitter/src/type/input-type-kind.ts diff --git a/packages/http-client-csharp/src/type/input-type-serialization-format.ts b/packages/http-client-csharp/emitter/src/type/input-type-serialization-format.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-type-serialization-format.ts rename to packages/http-client-csharp/emitter/src/type/input-type-serialization-format.ts diff --git a/packages/http-client-csharp/src/type/input-type-value.ts b/packages/http-client-csharp/emitter/src/type/input-type-value.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-type-value.ts rename to packages/http-client-csharp/emitter/src/type/input-type-value.ts diff --git a/packages/http-client-csharp/src/type/input-type.ts b/packages/http-client-csharp/emitter/src/type/input-type.ts similarity index 100% rename from packages/http-client-csharp/src/type/input-type.ts rename to packages/http-client-csharp/emitter/src/type/input-type.ts diff --git a/packages/http-client-csharp/src/type/literal-type-context.ts b/packages/http-client-csharp/emitter/src/type/literal-type-context.ts similarity index 100% rename from packages/http-client-csharp/src/type/literal-type-context.ts rename to packages/http-client-csharp/emitter/src/type/literal-type-context.ts diff --git a/packages/http-client-csharp/src/type/operation-final-state-via.ts b/packages/http-client-csharp/emitter/src/type/operation-final-state-via.ts similarity index 100% rename from packages/http-client-csharp/src/type/operation-final-state-via.ts rename to packages/http-client-csharp/emitter/src/type/operation-final-state-via.ts diff --git a/packages/http-client-csharp/src/type/operation-long-running.ts b/packages/http-client-csharp/emitter/src/type/operation-long-running.ts similarity index 100% rename from packages/http-client-csharp/src/type/operation-long-running.ts rename to packages/http-client-csharp/emitter/src/type/operation-long-running.ts diff --git a/packages/http-client-csharp/src/type/operation-paging.ts b/packages/http-client-csharp/emitter/src/type/operation-paging.ts similarity index 100% rename from packages/http-client-csharp/src/type/operation-paging.ts rename to packages/http-client-csharp/emitter/src/type/operation-paging.ts diff --git a/packages/http-client-csharp/src/type/operation-response.ts b/packages/http-client-csharp/emitter/src/type/operation-response.ts similarity index 100% rename from packages/http-client-csharp/src/type/operation-response.ts rename to packages/http-client-csharp/emitter/src/type/operation-response.ts diff --git a/packages/http-client-csharp/src/type/protocols.ts b/packages/http-client-csharp/emitter/src/type/protocols.ts similarity index 100% rename from packages/http-client-csharp/src/type/protocols.ts rename to packages/http-client-csharp/emitter/src/type/protocols.ts diff --git a/packages/http-client-csharp/src/type/request-location.ts b/packages/http-client-csharp/emitter/src/type/request-location.ts similarity index 100% rename from packages/http-client-csharp/src/type/request-location.ts rename to packages/http-client-csharp/emitter/src/type/request-location.ts diff --git a/packages/http-client-csharp/src/type/request-method.ts b/packages/http-client-csharp/emitter/src/type/request-method.ts similarity index 100% rename from packages/http-client-csharp/src/type/request-method.ts rename to packages/http-client-csharp/emitter/src/type/request-method.ts diff --git a/packages/http-client-csharp/src/type/usage.ts b/packages/http-client-csharp/emitter/src/type/usage.ts similarity index 100% rename from packages/http-client-csharp/src/type/usage.ts rename to packages/http-client-csharp/emitter/src/type/usage.ts diff --git a/packages/http-client-csharp/src/type/validation-type.ts b/packages/http-client-csharp/emitter/src/type/validation-type.ts similarity index 100% rename from packages/http-client-csharp/src/type/validation-type.ts rename to packages/http-client-csharp/emitter/src/type/validation-type.ts diff --git a/packages/http-client-csharp/test/Unit/encode.test.ts b/packages/http-client-csharp/emitter/test/Unit/encode.test.ts similarity index 100% rename from packages/http-client-csharp/test/Unit/encode.test.ts rename to packages/http-client-csharp/emitter/test/Unit/encode.test.ts diff --git a/packages/http-client-csharp/test/Unit/model-type.test.ts b/packages/http-client-csharp/emitter/test/Unit/model-type.test.ts similarity index 100% rename from packages/http-client-csharp/test/Unit/model-type.test.ts rename to packages/http-client-csharp/emitter/test/Unit/model-type.test.ts diff --git a/packages/http-client-csharp/test/Unit/operation-final-state-via.test.ts b/packages/http-client-csharp/emitter/test/Unit/operation-final-state-via.test.ts similarity index 100% rename from packages/http-client-csharp/test/Unit/operation-final-state-via.test.ts rename to packages/http-client-csharp/emitter/test/Unit/operation-final-state-via.test.ts diff --git a/packages/http-client-csharp/test/Unit/property-type.test.ts b/packages/http-client-csharp/emitter/test/Unit/property-type.test.ts similarity index 100% rename from packages/http-client-csharp/test/Unit/property-type.test.ts rename to packages/http-client-csharp/emitter/test/Unit/property-type.test.ts diff --git a/packages/http-client-csharp/test/Unit/scalar.test.ts b/packages/http-client-csharp/emitter/test/Unit/scalar.test.ts similarity index 100% rename from packages/http-client-csharp/test/Unit/scalar.test.ts rename to packages/http-client-csharp/emitter/test/Unit/scalar.test.ts diff --git a/packages/http-client-csharp/test/Unit/string-format.test.ts b/packages/http-client-csharp/emitter/test/Unit/string-format.test.ts similarity index 100% rename from packages/http-client-csharp/test/Unit/string-format.test.ts rename to packages/http-client-csharp/emitter/test/Unit/string-format.test.ts diff --git a/packages/http-client-csharp/test/Unit/usage.test.ts b/packages/http-client-csharp/emitter/test/Unit/usage.test.ts similarity index 100% rename from packages/http-client-csharp/test/Unit/usage.test.ts rename to packages/http-client-csharp/emitter/test/Unit/usage.test.ts diff --git a/packages/http-client-csharp/test/Unit/utils/test-util.ts b/packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts similarity index 100% rename from packages/http-client-csharp/test/Unit/utils/test-util.ts rename to packages/http-client-csharp/emitter/test/Unit/utils/test-util.ts diff --git a/packages/http-client-csharp/tsconfig.build.json b/packages/http-client-csharp/emitter/tsconfig.build.json similarity index 100% rename from packages/http-client-csharp/tsconfig.build.json rename to packages/http-client-csharp/emitter/tsconfig.build.json diff --git a/packages/http-client-csharp/tsconfig.json b/packages/http-client-csharp/emitter/tsconfig.json similarity index 53% rename from packages/http-client-csharp/tsconfig.json rename to packages/http-client-csharp/emitter/tsconfig.json index d9d7cca1f2..ff45a592dd 100644 --- a/packages/http-client-csharp/tsconfig.json +++ b/packages/http-client-csharp/emitter/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "noEmit": true } diff --git a/packages/http-client-csharp/vitest.config.ts b/packages/http-client-csharp/emitter/vitest.config.ts similarity index 63% rename from packages/http-client-csharp/vitest.config.ts rename to packages/http-client-csharp/emitter/vitest.config.ts index 15eeaceb85..0e0e86fefc 100644 --- a/packages/http-client-csharp/vitest.config.ts +++ b/packages/http-client-csharp/emitter/vitest.config.ts @@ -1,4 +1,4 @@ import { defineConfig, mergeConfig } from "vitest/config"; -import { defaultTypeSpecVitestConfig } from "../../vitest.workspace.js"; +import { defaultTypeSpecVitestConfig } from "../../../vitest.workspace.js"; export default mergeConfig(defaultTypeSpecVitestConfig, defineConfig({})); diff --git a/packages/http-client-csharp/eng/CodeAnalysis.ruleset b/packages/http-client-csharp/eng/CodeAnalysis.ruleset new file mode 100644 index 0000000000..620d8b708a --- /dev/null +++ b/packages/http-client-csharp/eng/CodeAnalysis.ruleset @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/http-client-csharp/eng/images/packageIcon.png b/packages/http-client-csharp/eng/images/packageIcon.png new file mode 100644 index 0000000000..9a6227a73b Binary files /dev/null and b/packages/http-client-csharp/eng/images/packageIcon.png differ diff --git a/packages/http-client-csharp/eng/scripts/Build-Packages.ps1 b/packages/http-client-csharp/eng/scripts/Build-Packages.ps1 index 2a568da228..832fa5b9fe 100644 --- a/packages/http-client-csharp/eng/scripts/Build-Packages.ps1 +++ b/packages/http-client-csharp/eng/scripts/Build-Packages.ps1 @@ -34,6 +34,10 @@ if ($BuildNumber) { Push-Location "$packageRoot" try { Write-Host "Working in $PWD" + + # TODO this is happening 4 times until ..... gets fixed + Invoke-LoggedCommand "dotnet build ./generator" -GroupOutput + Invoke-LoggedCommand "npm run build" -GroupOutput if ($BuildNumber) { diff --git a/packages/http-client-csharp/eng/scripts/Generate.ps1 b/packages/http-client-csharp/eng/scripts/Generate.ps1 new file mode 100644 index 0000000000..52c1e2580c --- /dev/null +++ b/packages/http-client-csharp/eng/scripts/Generate.ps1 @@ -0,0 +1,13 @@ +#Requires -Version 7.0 + +Import-Module "$PSScriptRoot\Generation.psm1" -DisableNameChecking -Force; + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..' '..' 'generator') +$mgcArtifactRoot = Join-Path $repoRoot 'artifacts' 'bin' 'Microsoft.Generator.CSharp' 'Debug' 'net8.0' +$clientModelTestProjectsDirectory = Join-Path $repoRoot 'Microsoft.Generator.CSharp.ClientModel.TestProjects' + +Invoke "dotnet build $repoRoot" + +$mgcPath = Join-Path $mgcArtifactRoot "Microsoft.Generator.CSharp" +$unbrandedTypespecTestProject = Join-Path $clientModelTestProjectsDirectory "Unbranded-TypeSpec" +Invoke "$mgcPath $unbrandedTypespecTestProject" diff --git a/packages/http-client-csharp/eng/scripts/Generation.psm1 b/packages/http-client-csharp/eng/scripts/Generation.psm1 new file mode 100644 index 0000000000..5f69ef0534 --- /dev/null +++ b/packages/http-client-csharp/eng/scripts/Generation.psm1 @@ -0,0 +1,22 @@ +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..') + +function Invoke($command, $executePath=$repoRoot) +{ + Write-Host "> $command" + Push-Location $executePath + if ($IsLinux -or $IsMacOs) + { + sh -c "$command 2>&1" + } + else + { + cmd /c "$command 2>&1" + } + Pop-Location + + if($LastExitCode -ne 0) + { + Write-Error "Command failed to execute: $command" + } +} +Export-ModuleMember -Function "Invoke" diff --git a/packages/http-client-csharp/eng/scripts/Initialize-Repository.ps1 b/packages/http-client-csharp/eng/scripts/Initialize-Repository.ps1 index 259d3e2b63..5b9d64f0f3 100644 --- a/packages/http-client-csharp/eng/scripts/Initialize-Repository.ps1 +++ b/packages/http-client-csharp/eng/scripts/Initialize-Repository.ps1 @@ -17,6 +17,17 @@ try { Remove-Item -Recurse -Force "./node_modules" } + # install dotnet + if ($IsWindows) { + # download and run https://dot.net/v1/dotnet-install.ps1 + Invoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1' -OutFile 'dotnet-install.ps1' + ./dotnet-install.ps1 -Version '8.0.204' + } + else { + Invoke-WebRequest 'https://dot.net/v1/dotnet-install.sh' -OutFile 'dotnet-install.sh' + bash ./dotnet-install.sh --version 8.0.204 + } + # install and list npm packages if ($BuildArtifactsPath) { diff --git a/packages/http-client-csharp/eng/scripts/Test-Packages.ps1 b/packages/http-client-csharp/eng/scripts/Test-Packages.ps1 index af82147d4f..f67755f7b7 100644 --- a/packages/http-client-csharp/eng/scripts/Test-Packages.ps1 +++ b/packages/http-client-csharp/eng/scripts/Test-Packages.ps1 @@ -15,11 +15,14 @@ Set-ConsoleEncoding Push-Location $packageRoot try { if ($UnitTests) { - # test the emitter Push-Location "$packageRoot" try { + # test the emitter Invoke-LoggedCommand "npm run build" -GroupOutput Invoke-LoggedCommand "npm run test" -GroupOutput + + # test the generator + Invoke-LoggedCommand "dotnet test ./generator" -GroupOutput } finally { Pop-Location diff --git a/packages/http-client-csharp/eng/stylecop.json b/packages/http-client-csharp/eng/stylecop.json new file mode 100644 index 0000000000..846f4b7624 --- /dev/null +++ b/packages/http-client-csharp/eng/stylecop.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "copyrightText": "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} License.", + "companyName": "Microsoft Corporation", + "xmlHeader": false, + "variables": { + "licenseName": "MIT" + } + } + } +} diff --git a/packages/http-client-csharp/generator/.editorconfig b/packages/http-client-csharp/generator/.editorconfig new file mode 100644 index 0000000000..01850bd6c4 --- /dev/null +++ b/packages/http-client-csharp/generator/.editorconfig @@ -0,0 +1,201 @@ +# editorconfig.org + +[*] +trim_trailing_whitespace = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[**] +insert_final_newline = true +indent_style = space +indent_size = 4 + +# C# files +[**.cs] +# New line preferences +csharp_new_line_before_open_brace = all # vs-default: any +csharp_new_line_before_else = true # vs-default: true +csharp_new_line_before_catch = true # vs-default: true +csharp_new_line_before_finally = true # vs-default: true +csharp_new_line_before_members_in_object_initializers = true # vs-default: true +csharp_new_line_before_members_in_anonymous_types = true # vs-default: true +csharp_new_line_between_query_expression_clauses = true # vs-default: true + +# Indentation preferences +csharp_indent_block_contents = true # vs-default: true +csharp_indent_braces = false # vs-default: false +csharp_indent_case_contents = true # vs-default: true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true # vs-default: true +csharp_indent_labels = one_less_than_current # vs-default: one_less_than_current + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion # vs-default: false:none +dotnet_style_qualification_for_property = false:suggestion # vs-default: false:none +dotnet_style_qualification_for_method = false:suggestion # vs-default: false:none +dotnet_style_qualification_for_event = false:suggestion # vs-default: false:none + +# only use var when it's obvious what the variable type is +csharp_style_var_for_built_in_types = false:none # vs-default: true:none +csharp_style_var_when_type_is_apparent = false:none # vs-default: true:none +csharp_style_var_elsewhere = false:suggestion # vs-default: true:none + +# use language keywords instead of BCL types +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion # vs-default: true:none +dotnet_style_predefined_type_for_member_access = true:suggestion # vs-default: true:none + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# name all properties using PascalCase +dotnet_naming_rule.properties_should_be_pascal_case.severity = error +dotnet_naming_rule.properties_should_be_pascal_case.symbols = properties_symbols +dotnet_naming_rule.properties_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.properties_symbols.applicable_kinds = property + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style + +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal + +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_sort_system_directives_first = true # vs-default: true +csharp_prefer_braces = true:refactoring +csharp_preserve_single_line_blocks = true # vs-default: true +csharp_preserve_single_line_statements = false # vs-default: true +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion + +# Code quality +dotnet_style_readonly_field = true:suggestion +dotnet_code_quality_unused_parameters = non_public:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion # vs-default: true:suggestion +dotnet_style_collection_initializer = true:suggestion # vs-default: true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion # vs-default: true:suggestion +dotnet_style_coalesce_expression = true:suggestion # vs-default: true:suggestion +dotnet_style_null_propagation = true:suggestion # vs-default: true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:refactoring +dotnet_style_prefer_conditional_expression_over_return = true:refactoring +csharp_prefer_simple_default_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = false:none # vs-default: false:none +csharp_style_expression_bodied_constructors = false:none # vs-default: false:none +csharp_style_expression_bodied_operators = false:none # vs-default: false:none +csharp_style_expression_bodied_properties = true:none # vs-default: true:none +csharp_style_expression_bodied_indexers = true:none # vs-default: true:none +csharp_style_expression_bodied_accessors = true:none # vs-default: true:none +csharp_style_expression_bodied_lambdas = true:refactoring +csharp_style_expression_bodied_local_functions = true:refactoring + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion # vs-default: true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion # vs-default: true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion # vs-default: true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion # vs-default: true:suggestion +csharp_style_conditional_delegate_call = true:suggestion # vs-default: true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = false # vs-default: false +csharp_space_after_colon_in_inheritance_clause = true # vs-default: true +csharp_space_after_comma = true # vs-default: true +csharp_space_after_dot = false # vs-default: false +csharp_space_after_keywords_in_control_flow_statements = true # vs-default: true +csharp_space_after_semicolon_in_for_statement = true # vs-default: true +csharp_space_around_binary_operators = before_and_after # vs-default: before_and_after +csharp_space_around_declaration_statements = do_not_ignore # vs-default: false +csharp_space_before_colon_in_inheritance_clause = true # vs-default: true +csharp_space_before_comma = false # vs-default: false +csharp_space_before_dot = false # vs-default: false +csharp_space_before_open_square_brackets = false # vs-default: false +csharp_space_before_semicolon_in_for_statement = false # vs-default: false +csharp_space_between_empty_square_brackets = false # vs-default: false +csharp_space_between_method_call_empty_parameter_list_parentheses = false # vs-default: false +csharp_space_between_method_call_name_and_opening_parenthesis = false # vs-default: false +csharp_space_between_method_call_parameter_list_parentheses = false # vs-default: false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false # vs-default: false +csharp_space_between_method_declaration_name_and_open_parenthesis = false # vs-default: false +csharp_space_between_method_declaration_parameter_list_parentheses = false # vs-default: false +csharp_space_between_parentheses = false # vs-default: false +csharp_space_between_square_brackets = false # vs-default: false + +# Require accessibility modifiers +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion # vs-default: for_non_interface_members:none + +# Analyzers +dotnet_code_quality.ca1802.api_surface = private, internal + +# Warn about unused private fields & methods +dotnet_diagnostic.IDE0051.severity = warning + +# Xml project files +[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd, bat}] +end_of_line = crlf + +# Yaml files +[*.{yml,yaml}] +indent_size = 2 diff --git a/packages/http-client-csharp/generator/Directory.Build.props b/packages/http-client-csharp/generator/Directory.Build.props new file mode 100644 index 0000000000..89e091b803 --- /dev/null +++ b/packages/http-client-csharp/generator/Directory.Build.props @@ -0,0 +1,55 @@ + + + $(MSBuildThisFileDirectory) + $(MSBuildThisFileDirectory)../eng + true + $(MSBuildThisFileDirectory)Packages.Data.props + true + + + + packageIcon.png + $(RepoEngPath)/images/$(PackageIcon) + + + + + Debug + AnyCPU + $(Platform) + + + + + $(RepoRoot)artifacts\ + $(ArtifactsDir)obj\ + $(ArtifactsDir)bin\ + $(ArtifactsDir)packages\$(Configuration)\ + + $(NoWarn);NU5105 + true + $(MSBuildProjectName) + + $([System.IO.Path]::GetFullPath('$(ArtifactsBinDir)$(OutDirName)\')) + $(BaseOutputPath)$(Configuration)\ + $(BaseOutputPath)$(PlatformName)\$(Configuration)\ + + $([System.IO.Path]::GetFullPath('$(ArtifactsObjDir)$(OutDirName)\')) + $(BaseIntermediateOutputPath)$(Configuration)\ + $(BaseIntermediateOutputPath)$(PlatformName)\$(Configuration)\ + + $(ArtifactsPackagesDir) + $(RepoEngPath)\CodeAnalysis.ruleset + + + + false + false + false + 12.0 + + + diff --git a/packages/http-client-csharp/generator/Directory.Build.targets b/packages/http-client-csharp/generator/Directory.Build.targets new file mode 100644 index 0000000000..290ad8754b --- /dev/null +++ b/packages/http-client-csharp/generator/Directory.Build.targets @@ -0,0 +1,17 @@ + + + + + all + runtime; build; native; contentfiles; analyzers + + + false + + + + + + + + diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Microsoft.Generator.CSharp.ClientModel.TestProjects.csproj b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Microsoft.Generator.CSharp.ClientModel.TestProjects.csproj new file mode 100644 index 0000000000..a601507255 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Microsoft.Generator.CSharp.ClientModel.TestProjects.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + false + enable + + + + + diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/Configuration.json b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/Configuration.json new file mode 100644 index 0000000000..c505454e1b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/Configuration.json @@ -0,0 +1,5 @@ +{ + "output-folder": ".", + "namespace": "UnbrandedTypeSpec", + "library-name": "UnbrandedTypeSpec" +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/Unbranded-TypeSpec.tsp b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/Unbranded-TypeSpec.tsp new file mode 100644 index 0000000000..56ad3aca1c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/Unbranded-TypeSpec.tsp @@ -0,0 +1,351 @@ +import "@typespec/rest"; +import "@typespec/http"; +import "@azure-tools/typespec-client-generator-core"; +import "@azure-tools/typespec-azure-core"; + +@service({ + title: "hello world", + version: "0.1.0", +}) +@doc("This is a sample typespec project.") +@server( + "{unbrandedTypeSpecUrl}", + "Endpoint Service", + { + unbrandedTypeSpecUrl: string, + } +) +@useAuth(ApiKeyAuth) +namespace UnbrandedTypeSpec; + +using TypeSpec.Http; +using Azure.ClientGenerator.Core; +using Azure.Core; + +@doc("float fixed enum") +@fixed +enum FloatFixedEnum { + One: 1.1, + Two: 2.2, + Four: 4.4, +} + +@doc("int fixed enum") +@fixed +enum IntFixedEnum { + One: 1, + Two: 2, + Four: 4, +} + +@doc("Simple enum") +@fixed +enum StringFixedEnum { + One: "1", + Two: "2", + Four: "4", +} + +@doc("Int based extensible enum") +union IntExtensibleEnum { + int32, + One: 1, + Two: 2, + Four: 4, +} + +@doc("Float based extensible enum") +union FloatExtensibleEnum { + float32, + One: 1.0, + Two: 2.0, + Four: 4.0, +} + +@doc("Extensible enum") +union StringExtensibleEnum { + string, + One: "1", + Two: "2", + Four: "4", +} + +@doc("A model with a few properties of literal types") +model Thing { + @doc("name of the Thing") + name: string; + + @doc("required Union") + requiredUnion: string | string[] | int32; + + @doc("required literal string") + requiredLiteralString: "accept"; + + @doc("required literal int") + requiredLiteralInt: 123; + + @doc("required literal float") + requiredLiteralFloat: 1.23; + + @doc("required literal bool") + requiredLiteralBool: false; + + @doc("optional literal string") + optionalLiteralString?: "reject"; + + @doc("optional literal int") + optionalLiteralInt?: 456; + + @doc("optional literal float") + optionalLiteralFloat?: 4.56; + + @doc("optional literal bool") + optionalLiteralBool?: true; + + @doc("description with xml <|endoftext|>") + requiredBadDescription: string; + + @doc("optional nullable collection") + optionalNullableList?: int32[] | null; + + @doc("required nullable collection") + requiredNullableList: int32[] | null; +} + +@doc("A model with a few required nullable properties") +model ModelWithRequiredNullableProperties { + @doc("required nullable primitive type") + requiredNullablePrimitive: int32 | null; + + @doc("required nullable extensible enum type") + requiredExtensibleEnum: StringExtensibleEnum | null; + + @doc("required nullable fixed enum type") + requiredFixedEnum: StringFixedEnum | null; +} + +@doc("this is not a friendly model but with a friendly name") +@friendlyName("Friend") +model NotFriend { + @doc("name of the NotFriend") + name: string; +} + +@doc("this is a model with a projected name") +@projectedName("client", "ProjectedModel") +model ModelWithProjectedName { + @doc("name of the ModelWithProjectedName") + name: string; +} + +@doc("this is a roundtrip model") +model RoundTripModel { + @doc("Required string, illustrating a reference type property.") + requiredString: string; + + @doc("Required int, illustrating a value type property.") + requiredInt: int32; + + @doc("Required collection of enums") + requiredCollection: StringFixedEnum[]; + + @doc("Required dictionary of enums") + requiredDictionary: Record; + + @doc("Required model") + requiredModel: Thing; + + @doc("this is an int based extensible enum") + intExtensibleEnum?: IntExtensibleEnum; + + @doc("this is a collection of int based extensible enum") + intExtensibleEnumCollection?: IntExtensibleEnum[]; + + @doc("this is a float based extensible enum") + floatExtensibleEnum?: FloatExtensibleEnum; + + @doc("this is a collection of float based extensible enum") + floatExtensibleEnumCollection?: FloatExtensibleEnum[]; + + @doc("this is a float based fixed enum") + floatFixedEnum?: FloatFixedEnum; + + @doc("this is a collection of float based fixed enum") + floatFixedEnumCollection?: FloatFixedEnum[]; + + @doc("this is a int based fixed enum") + intFixedEnum?: IntFixedEnum; + + @doc("this is a collection of int based fixed enum") + intFixedEnumCollection?: IntFixedEnum[]; + + @doc("this is a string based fixed enum") + stringFixedEnum?: StringFixedEnum; + + @doc("required unknown") + requiredUnknown: unknown; + + @doc("optional unknown") + optionalUnknown?: unknown; + + @doc("required record of unknown") + requiredRecordUnknown: Record; + + @doc("optional record of unknown") + optionalRecordUnknown?: Record; + + @doc("required readonly record of unknown") + @visibility("read") + readOnlyRequiredRecordUnknown: Record; + + @doc("optional readonly record of unknown") + @visibility("read") + readOnlyOptionalRecordUnknown?: Record; + + @doc("this is a model with required nullable properties") + modelWithRequiredNullable: ModelWithRequiredNullableProperties; +} + +union DaysOfWeekExtensibleEnum { + string, + Monday: "Monday", + Tuesday: "Tuesday", + Wednesday: "Wednesday", + Thursday: "Thursday", + Friday: "Friday", + Saturday: "Saturday", + Sunday: "Sunday", +} + +model ModelWithFormat { + @doc("url format") + @format("Uri") + sourceUrl: string; + + @doc("uuid format") + @format("uuid") + guid: string; +} + +@route("/hello") +@doc("Return hi") +@get +op sayHi( + @header headParameter: string, + @query queryParameter: string, + @query optionalQuery?: string, +): Thing; + +@route("/againHi") +@doc("Return hi again") +@get +@convenientAPI(true) +op helloAgain( + @header p1: string, + @body action: RoundTripModel, + @header contentType: "text/plain", + @path p2: string, +): RoundTripModel; + +@route("/noContentType") +@doc("Return hi again") +@get +@convenientAPI(false) +op noContentType( + @header p1: string, + @body action: RoundTripModel, + @path p2: string, +): RoundTripModel; + +@route("/demoHi") +@doc("Return hi in demo2") +@get +@convenientAPI(true) +op helloDemo2(): Thing; + +@route("/literal") +@doc("Create with literal value") +@post +@convenientAPI(true) +op createLiteral(@body body: Thing): Thing; + +@route("/helloLiteral") +@doc("Send literal parameters") +@get +@convenientAPI(true) +op helloLiteral(@header p1: "test", @path p2: 123, @query p3: true): Thing; + +@route("/top") +@doc("top level method") +@get +@convenientAPI(true) +op topAction(@path @format("date") action: string): Thing; + +@route("/top2") +@doc("top level method2") +@get +@convenientAPI(false) +op topAction2(): Thing; + +@route("/patch") +@doc("top level patch") +@patch +@convenientAPI(true) +op patchAction(@body body: Thing): Thing; + +@route("/anonymousBody") +@doc("body parameter without body decorator") +@post +@convenientAPI(true) +op anonymousBody(...Thing): Thing; + +@route("/friendlyName") +@doc("Model can have its friendly name") +@post +@convenientAPI(true) +op friendlyModel(...NotFriend): NotFriend; + +op addTimeHeader(@header("Repeatability-First-Sent") repeatabilityFirstSent?: utcDateTime): void; + +@route("/stringFormat") +@doc("parameter has string format.") +@post +@convenientAPI(true) +op stringFormat(@path @format("uuid") subscriptionId: string, @body body: ModelWithFormat): void; + +@route("/projectedName") +@doc("Model can have its projected name") +@post +@convenientAPI(true) +op projectedNameModel(...ModelWithProjectedName): ModelWithProjectedName; + +@route("/returnsAnonymousModel") +@doc("return anonymous model") +@post +@convenientAPI(true) +op returnsAnonymousModel(): {}; + +@get +@route("/unknown-value") +@doc("get extensible enum") +op getUnknownValue(): DaysOfWeekExtensibleEnum; + +@doc("When set protocol false and convenient true, then the protocol method should be internal") +@route("internalProtocol") +@post +@convenientAPI(true) +@protocolAPI(false) +op internalProtocol(@body body: Thing): Thing; + +@doc("When set protocol false and convenient true, the convenient method should be generated even it has the same signature as protocol one") +@route("stillConvenient") +@get +@convenientAPI(true) +@protocolAPI(false) +op stillConvenient(): void; + +@route("/headAsBoolean") +@doc("head as boolean.") +@head +@convenientAPI(true) +op headAsBoolean(@path id: string): void; diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/UnbrandedTypeSpec.sln b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/UnbrandedTypeSpec.sln new file mode 100644 index 0000000000..00547e30c4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/UnbrandedTypeSpec.sln @@ -0,0 +1,50 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29709.97 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnbrandedTypeSpec", "src\UnbrandedTypeSpec.csproj", "{28FF4005-4467-4E36-92E7-DEA27DEB1519}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnbrandedTypeSpec.Tests", "tests\UnbrandedTypeSpec.Tests.csproj", "{1F1CD1D4-9932-4B73-99D8-C252A67D4B46}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B0C276D1-2930-4887-B29A-D1A33E7009A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0C276D1-2930-4887-B29A-D1A33E7009A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0C276D1-2930-4887-B29A-D1A33E7009A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0C276D1-2930-4887-B29A-D1A33E7009A2}.Release|Any CPU.Build.0 = Release|Any CPU + {8E9A77AC-792A-4432-8320-ACFD46730401}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E9A77AC-792A-4432-8320-ACFD46730401}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E9A77AC-792A-4432-8320-ACFD46730401}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E9A77AC-792A-4432-8320-ACFD46730401}.Release|Any CPU.Build.0 = Release|Any CPU + {A4241C1F-A53D-474C-9E4E-075054407E74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4241C1F-A53D-474C-9E4E-075054407E74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4241C1F-A53D-474C-9E4E-075054407E74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4241C1F-A53D-474C-9E4E-075054407E74}.Release|Any CPU.Build.0 = Release|Any CPU + {FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA8BD3F1-8616-47B6-974C-7576CDF4717E}.Release|Any CPU.Build.0 = Release|Any CPU + {85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85677AD3-C214-42FA-AE6E-49B956CAC8DC}.Release|Any CPU.Build.0 = Release|Any CPU + {28FF4005-4467-4E36-92E7-DEA27DEB1519}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28FF4005-4467-4E36-92E7-DEA27DEB1519}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28FF4005-4467-4E36-92E7-DEA27DEB1519}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28FF4005-4467-4E36-92E7-DEA27DEB1519}.Release|Any CPU.Build.0 = Release|Any CPU + {1F1CD1D4-9932-4B73-99D8-C252A67D4B46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F1CD1D4-9932-4B73-99D8-C252A67D4B46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F1CD1D4-9932-4B73-99D8-C252A67D4B46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F1CD1D4-9932-4B73-99D8-C252A67D4B46}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A97F4B90-2591-4689-B1F8-5F21FE6D6CAE} + EndGlobalSection +EndGlobal diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/Friend.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/Friend.cs new file mode 100644 index 0000000000..ab15e63ff2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/Friend.cs @@ -0,0 +1,19 @@ +// + +#nullable disable + +namespace UnbrandedTypeSpec.Models +{ + public partial class Friend + { + // Add Fields + + // Add Constructors + + // Add Properties + + // Add Methods + + // Add Nested Type + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ModelWithFormat.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ModelWithFormat.cs new file mode 100644 index 0000000000..8f76547168 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ModelWithFormat.cs @@ -0,0 +1,19 @@ +// + +#nullable disable + +namespace UnbrandedTypeSpec.Models +{ + public partial class ModelWithFormat + { + // Add Fields + + // Add Constructors + + // Add Properties + + // Add Methods + + // Add Nested Type + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.cs new file mode 100644 index 0000000000..018763577b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.cs @@ -0,0 +1,19 @@ +// + +#nullable disable + +namespace UnbrandedTypeSpec.Models +{ + public partial class ModelWithRequiredNullableProperties + { + // Add Fields + + // Add Constructors + + // Add Properties + + // Add Methods + + // Add Nested Type + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.cs new file mode 100644 index 0000000000..421cb4dec6 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.cs @@ -0,0 +1,19 @@ +// + +#nullable disable + +namespace UnbrandedTypeSpec.Models +{ + public partial class ProjectedModel + { + // Add Fields + + // Add Constructors + + // Add Properties + + // Add Methods + + // Add Nested Type + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.cs new file mode 100644 index 0000000000..803e2c6e21 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.cs @@ -0,0 +1,19 @@ +// + +#nullable disable + +namespace UnbrandedTypeSpec.Models +{ + public partial class ReturnsAnonymousModelResponse + { + // Add Fields + + // Add Constructors + + // Add Properties + + // Add Methods + + // Add Nested Type + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.cs new file mode 100644 index 0000000000..f0a0cce2aa --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.cs @@ -0,0 +1,19 @@ +// + +#nullable disable + +namespace UnbrandedTypeSpec.Models +{ + public partial class RoundTripModel + { + // Add Fields + + // Add Constructors + + // Add Properties + + // Add Methods + + // Add Nested Type + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/Thing.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/Thing.cs new file mode 100644 index 0000000000..e5fad142cb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/src/Generated/Models/Thing.cs @@ -0,0 +1,19 @@ +// + +#nullable disable + +namespace UnbrandedTypeSpec.Models +{ + public partial class Thing + { + // Add Fields + + // Add Constructors + + // Add Properties + + // Add Methods + + // Add Nested Type + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/tspCodeModel.json b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/tspCodeModel.json new file mode 100644 index 0000000000..2d8e966996 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/tspCodeModel.json @@ -0,0 +1,2923 @@ +{ + "$id": "1", + "Name": "UnbrandedTypeSpec", + "Description": "This is a sample typespec project.", + "ApiVersions": [], + "Enums": [ + { + "$id": "2", + "Kind": "Enum", + "Name": "Thing_requiredLiteralString", + "EnumValueType": "String", + "AllowedValues": [ + { + "$id": "3", + "Name": "accept", + "Value": "accept", + "Description": "accept" + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "The Thing_requiredLiteralString", + "IsExtensible": true, + "IsNullable": false, + "Usage": "RoundTrip" + }, + { + "$id": "4", + "Kind": "Enum", + "Name": "Thing_requiredLiteralInt", + "EnumValueType": "Float32", + "AllowedValues": [ + { + "$id": "5", + "Name": "123", + "Value": 123, + "Description": "123" + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "The Thing_requiredLiteralInt", + "IsExtensible": true, + "IsNullable": false, + "Usage": "RoundTrip" + }, + { + "$id": "6", + "Kind": "Enum", + "Name": "Thing_requiredLiteralFloat", + "EnumValueType": "Float32", + "AllowedValues": [ + { + "$id": "7", + "Name": "1.23", + "Value": 1.23, + "Description": "1.23" + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "The Thing_requiredLiteralFloat", + "IsExtensible": true, + "IsNullable": false, + "Usage": "RoundTrip" + }, + { + "$id": "8", + "Kind": "Enum", + "Name": "Thing_optionalLiteralString", + "EnumValueType": "String", + "AllowedValues": [ + { + "$id": "9", + "Name": "reject", + "Value": "reject", + "Description": "reject" + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "The Thing_optionalLiteralString", + "IsExtensible": true, + "IsNullable": false, + "Usage": "RoundTrip" + }, + { + "$id": "10", + "Kind": "Enum", + "Name": "Thing_optionalLiteralInt", + "EnumValueType": "Float32", + "AllowedValues": [ + { + "$id": "11", + "Name": "456", + "Value": 456, + "Description": "456" + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "The Thing_optionalLiteralInt", + "IsExtensible": true, + "IsNullable": false, + "Usage": "RoundTrip" + }, + { + "$id": "12", + "Kind": "Enum", + "Name": "Thing_optionalLiteralFloat", + "EnumValueType": "Float32", + "AllowedValues": [ + { + "$id": "13", + "Name": "4.56", + "Value": 4.56, + "Description": "4.56" + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "The Thing_optionalLiteralFloat", + "IsExtensible": true, + "IsNullable": false, + "Usage": "RoundTrip" + }, + { + "$id": "14", + "Kind": "Enum", + "Name": "StringFixedEnum", + "EnumValueType": "String", + "AllowedValues": [ + { + "$id": "15", + "Name": "One", + "Value": "1" + }, + { + "$id": "16", + "Name": "Two", + "Value": "2" + }, + { + "$id": "17", + "Name": "Four", + "Value": "4" + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "Simple enum", + "IsExtensible": false, + "IsNullable": true, + "Usage": "RoundTrip" + }, + { + "$id": "18", + "Kind": "Enum", + "Name": "StringExtensibleEnum", + "EnumValueType": "string", + "AllowedValues": [ + { + "$id": "19", + "Name": "One", + "Value": "1" + }, + { + "$id": "20", + "Name": "Two", + "Value": "2" + }, + { + "$id": "21", + "Name": "Four", + "Value": "4" + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "Extensible enum", + "IsExtensible": true, + "IsNullable": true, + "Usage": "RoundTrip" + }, + { + "$id": "22", + "Kind": "Enum", + "Name": "IntExtensibleEnum", + "EnumValueType": "int32", + "AllowedValues": [ + { + "$id": "23", + "Name": "One", + "Value": 1 + }, + { + "$id": "24", + "Name": "Two", + "Value": 2 + }, + { + "$id": "25", + "Name": "Four", + "Value": 4 + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "Int based extensible enum", + "IsExtensible": true, + "IsNullable": false, + "Usage": "RoundTrip" + }, + { + "$id": "26", + "Kind": "Enum", + "Name": "FloatExtensibleEnum", + "EnumValueType": "float32", + "AllowedValues": [ + { + "$id": "27", + "Name": "One", + "Value": 1 + }, + { + "$id": "28", + "Name": "Two", + "Value": 2 + }, + { + "$id": "29", + "Name": "Four", + "Value": 4 + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "Float based extensible enum", + "IsExtensible": true, + "IsNullable": false, + "Usage": "RoundTrip" + }, + { + "$id": "30", + "Kind": "Enum", + "Name": "FloatFixedEnum", + "EnumValueType": "Float32", + "AllowedValues": [ + { + "$id": "31", + "Name": "One", + "Value": 1.1 + }, + { + "$id": "32", + "Name": "Two", + "Value": 2.2 + }, + { + "$id": "33", + "Name": "Four", + "Value": 4.4 + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "float fixed enum", + "IsExtensible": false, + "IsNullable": false, + "Usage": "RoundTrip" + }, + { + "$id": "34", + "Kind": "Enum", + "Name": "IntFixedEnum", + "EnumValueType": "Float32", + "AllowedValues": [ + { + "$id": "35", + "Name": "One", + "Value": 1 + }, + { + "$id": "36", + "Name": "Two", + "Value": 2 + }, + { + "$id": "37", + "Name": "Four", + "Value": 4 + } + ], + "Namespace": "UnbrandedTypeSpec", + "Description": "int fixed enum", + "IsExtensible": false, + "IsNullable": false, + "Usage": "RoundTrip" + } + ], + "Models": [ + { + "$id": "38", + "Kind": "Model", + "Name": "Thing", + "Namespace": "UnbrandedTypeSpec", + "Description": "A model with a few properties of literal types", + "IsNullable": false, + "Usage": "RoundTrip", + "Properties": [ + { + "$id": "39", + "Name": "name", + "SerializedName": "name", + "Description": "name of the Thing", + "Type": { + "$id": "40", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "41", + "Name": "requiredUnion", + "SerializedName": "requiredUnion", + "Description": "required Union", + "Type": { + "$id": "42", + "Kind": "Union", + "Name": "Union", + "UnionItemTypes": [ + { + "$id": "43", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + { + "$id": "44", + "Kind": "Array", + "Name": "Array", + "ElementType": { + "$id": "45", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "IsNullable": false + }, + { + "$id": "46", + "Kind": "Primitive", + "Name": "Int32", + "IsNullable": false + } + ], + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "47", + "Name": "requiredLiteralString", + "SerializedName": "requiredLiteralString", + "Description": "required literal string", + "Type": { + "$id": "48", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$ref": "2" + }, + "Value": "accept", + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "49", + "Name": "requiredLiteralInt", + "SerializedName": "requiredLiteralInt", + "Description": "required literal int", + "Type": { + "$id": "50", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$ref": "4" + }, + "Value": 123, + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "51", + "Name": "requiredLiteralFloat", + "SerializedName": "requiredLiteralFloat", + "Description": "required literal float", + "Type": { + "$id": "52", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$ref": "6" + }, + "Value": 1.23, + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "53", + "Name": "requiredLiteralBool", + "SerializedName": "requiredLiteralBool", + "Description": "required literal bool", + "Type": { + "$id": "54", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$id": "55", + "Kind": "Primitive", + "Name": "Boolean", + "IsNullable": false + }, + "Value": false, + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "56", + "Name": "optionalLiteralString", + "SerializedName": "optionalLiteralString", + "Description": "optional literal string", + "Type": { + "$id": "57", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$ref": "8" + }, + "Value": "reject", + "IsNullable": false + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "58", + "Name": "optionalLiteralInt", + "SerializedName": "optionalLiteralInt", + "Description": "optional literal int", + "Type": { + "$id": "59", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$ref": "10" + }, + "Value": 456, + "IsNullable": false + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "60", + "Name": "optionalLiteralFloat", + "SerializedName": "optionalLiteralFloat", + "Description": "optional literal float", + "Type": { + "$id": "61", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$ref": "12" + }, + "Value": 4.56, + "IsNullable": false + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "62", + "Name": "optionalLiteralBool", + "SerializedName": "optionalLiteralBool", + "Description": "optional literal bool", + "Type": { + "$id": "63", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$id": "64", + "Kind": "Primitive", + "Name": "Boolean", + "IsNullable": false + }, + "Value": true, + "IsNullable": false + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "65", + "Name": "requiredBadDescription", + "SerializedName": "requiredBadDescription", + "Description": "description with xml <|endoftext|>", + "Type": { + "$id": "66", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "67", + "Name": "optionalNullableList", + "SerializedName": "optionalNullableList", + "Description": "optional nullable collection", + "Type": { + "$id": "68", + "Kind": "Array", + "Name": "Array", + "ElementType": { + "$id": "69", + "Kind": "Primitive", + "Name": "Int32", + "IsNullable": false + }, + "IsNullable": true + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "70", + "Name": "requiredNullableList", + "SerializedName": "requiredNullableList", + "Description": "required nullable collection", + "Type": { + "$id": "71", + "Kind": "Array", + "Name": "Array", + "ElementType": { + "$id": "72", + "Kind": "Primitive", + "Name": "Int32", + "IsNullable": false + }, + "IsNullable": true + }, + "IsRequired": true, + "IsReadOnly": false + } + ] + }, + { + "$id": "73", + "Kind": "Model", + "Name": "RoundTripModel", + "Namespace": "UnbrandedTypeSpec", + "Description": "this is a roundtrip model", + "IsNullable": false, + "Usage": "RoundTrip", + "Properties": [ + { + "$id": "74", + "Name": "requiredString", + "SerializedName": "requiredString", + "Description": "Required string, illustrating a reference type property.", + "Type": { + "$id": "75", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "76", + "Name": "requiredInt", + "SerializedName": "requiredInt", + "Description": "Required int, illustrating a value type property.", + "Type": { + "$id": "77", + "Kind": "Primitive", + "Name": "Int32", + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "78", + "Name": "requiredCollection", + "SerializedName": "requiredCollection", + "Description": "Required collection of enums", + "Type": { + "$id": "79", + "Kind": "Array", + "Name": "Array", + "ElementType": { + "$ref": "14" + }, + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "80", + "Name": "requiredDictionary", + "SerializedName": "requiredDictionary", + "Description": "Required dictionary of enums", + "Type": { + "$id": "81", + "Kind": "Dictionary", + "Name": "Dictionary", + "KeyType": { + "$id": "82", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "ValueType": { + "$ref": "18" + }, + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "83", + "Name": "requiredModel", + "SerializedName": "requiredModel", + "Description": "Required model", + "Type": { + "$ref": "38" + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "84", + "Name": "intExtensibleEnum", + "SerializedName": "intExtensibleEnum", + "Description": "this is an int based extensible enum", + "Type": { + "$ref": "22" + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "85", + "Name": "intExtensibleEnumCollection", + "SerializedName": "intExtensibleEnumCollection", + "Description": "this is a collection of int based extensible enum", + "Type": { + "$id": "86", + "Kind": "Array", + "Name": "Array", + "ElementType": { + "$ref": "22" + }, + "IsNullable": false + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "87", + "Name": "floatExtensibleEnum", + "SerializedName": "floatExtensibleEnum", + "Description": "this is a float based extensible enum", + "Type": { + "$ref": "26" + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "88", + "Name": "floatExtensibleEnumCollection", + "SerializedName": "floatExtensibleEnumCollection", + "Description": "this is a collection of float based extensible enum", + "Type": { + "$id": "89", + "Kind": "Array", + "Name": "Array", + "ElementType": { + "$ref": "26" + }, + "IsNullable": false + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "90", + "Name": "floatFixedEnum", + "SerializedName": "floatFixedEnum", + "Description": "this is a float based fixed enum", + "Type": { + "$ref": "30" + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "91", + "Name": "floatFixedEnumCollection", + "SerializedName": "floatFixedEnumCollection", + "Description": "this is a collection of float based fixed enum", + "Type": { + "$id": "92", + "Kind": "Array", + "Name": "Array", + "ElementType": { + "$ref": "30" + }, + "IsNullable": false + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "93", + "Name": "intFixedEnum", + "SerializedName": "intFixedEnum", + "Description": "this is a int based fixed enum", + "Type": { + "$ref": "34" + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "94", + "Name": "intFixedEnumCollection", + "SerializedName": "intFixedEnumCollection", + "Description": "this is a collection of int based fixed enum", + "Type": { + "$id": "95", + "Kind": "Array", + "Name": "Array", + "ElementType": { + "$ref": "34" + }, + "IsNullable": false + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "96", + "Name": "stringFixedEnum", + "SerializedName": "stringFixedEnum", + "Description": "this is a string based fixed enum", + "Type": { + "$ref": "14" + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "97", + "Name": "requiredUnknown", + "SerializedName": "requiredUnknown", + "Description": "required unknown", + "Type": { + "$id": "98", + "Kind": "Intrinsic", + "Name": "unknown", + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "99", + "Name": "optionalUnknown", + "SerializedName": "optionalUnknown", + "Description": "optional unknown", + "Type": { + "$id": "100", + "Kind": "Intrinsic", + "Name": "unknown", + "IsNullable": false + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "101", + "Name": "requiredRecordUnknown", + "SerializedName": "requiredRecordUnknown", + "Description": "required record of unknown", + "Type": { + "$id": "102", + "Kind": "Dictionary", + "Name": "Dictionary", + "KeyType": { + "$id": "103", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "ValueType": { + "$id": "104", + "Kind": "Intrinsic", + "Name": "unknown", + "IsNullable": false + }, + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "105", + "Name": "optionalRecordUnknown", + "SerializedName": "optionalRecordUnknown", + "Description": "optional record of unknown", + "Type": { + "$id": "106", + "Kind": "Dictionary", + "Name": "Dictionary", + "KeyType": { + "$id": "107", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "ValueType": { + "$id": "108", + "Kind": "Intrinsic", + "Name": "unknown", + "IsNullable": false + }, + "IsNullable": false + }, + "IsRequired": false, + "IsReadOnly": false + }, + { + "$id": "109", + "Name": "readOnlyRequiredRecordUnknown", + "SerializedName": "readOnlyRequiredRecordUnknown", + "Description": "required readonly record of unknown", + "Type": { + "$id": "110", + "Kind": "Dictionary", + "Name": "Dictionary", + "KeyType": { + "$id": "111", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "ValueType": { + "$id": "112", + "Kind": "Intrinsic", + "Name": "unknown", + "IsNullable": false + }, + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": true + }, + { + "$id": "113", + "Name": "readOnlyOptionalRecordUnknown", + "SerializedName": "readOnlyOptionalRecordUnknown", + "Description": "optional readonly record of unknown", + "Type": { + "$id": "114", + "Kind": "Dictionary", + "Name": "Dictionary", + "KeyType": { + "$id": "115", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "ValueType": { + "$id": "116", + "Kind": "Intrinsic", + "Name": "unknown", + "IsNullable": false + }, + "IsNullable": false + }, + "IsRequired": false, + "IsReadOnly": true + }, + { + "$id": "117", + "Name": "modelWithRequiredNullable", + "SerializedName": "modelWithRequiredNullable", + "Description": "this is a model with required nullable properties", + "Type": { + "$id": "118", + "Kind": "Model", + "Name": "ModelWithRequiredNullableProperties", + "Namespace": "UnbrandedTypeSpec", + "Description": "A model with a few required nullable properties", + "IsNullable": false, + "Usage": "RoundTrip", + "Properties": [ + { + "$id": "119", + "Name": "requiredNullablePrimitive", + "SerializedName": "requiredNullablePrimitive", + "Description": "required nullable primitive type", + "Type": { + "$id": "120", + "Kind": "Primitive", + "Name": "Int32", + "IsNullable": true + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "121", + "Name": "requiredExtensibleEnum", + "SerializedName": "requiredExtensibleEnum", + "Description": "required nullable extensible enum type", + "Type": { + "$ref": "18" + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "122", + "Name": "requiredFixedEnum", + "SerializedName": "requiredFixedEnum", + "Description": "required nullable fixed enum type", + "Type": { + "$ref": "14" + }, + "IsRequired": true, + "IsReadOnly": false + } + ] + }, + "IsRequired": true, + "IsReadOnly": false + } + ] + }, + { + "$ref": "118" + }, + { + "$id": "123", + "Kind": "Model", + "Name": "Friend", + "Namespace": "UnbrandedTypeSpec", + "Description": "this is not a friendly model but with a friendly name", + "IsNullable": false, + "Usage": "RoundTrip", + "Properties": [ + { + "$id": "124", + "Name": "name", + "SerializedName": "name", + "Description": "name of the NotFriend", + "Type": { + "$id": "125", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + } + ] + }, + { + "$id": "126", + "Kind": "Model", + "Name": "ModelWithFormat", + "Namespace": "UnbrandedTypeSpec", + "IsNullable": false, + "Usage": "Input", + "Properties": [ + { + "$id": "127", + "Name": "sourceUrl", + "SerializedName": "sourceUrl", + "Description": "url format", + "Type": { + "$id": "128", + "Kind": "Primitive", + "Name": "Uri", + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + }, + { + "$id": "129", + "Name": "guid", + "SerializedName": "guid", + "Description": "uuid format", + "Type": { + "$id": "130", + "Kind": "Primitive", + "Name": "Guid", + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + } + ] + }, + { + "$id": "131", + "Kind": "Model", + "Name": "ProjectedModel", + "Namespace": "UnbrandedTypeSpec", + "Description": "this is a model with a projected name", + "IsNullable": false, + "Usage": "RoundTrip", + "Properties": [ + { + "$id": "132", + "Name": "name", + "SerializedName": "name", + "Description": "name of the ModelWithProjectedName", + "Type": { + "$id": "133", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "IsRequired": true, + "IsReadOnly": false + } + ] + }, + { + "$id": "134", + "Kind": "Model", + "Name": "ReturnsAnonymousModelResponse", + "Namespace": "UnbrandedTypeSpec", + "IsNullable": false, + "Usage": "Output", + "Properties": [] + } + ], + "Clients": [ + { + "$id": "135", + "Name": "UnbrandedTypeSpecClient", + "Description": "This is a sample typespec project.", + "Operations": [ + { + "$id": "136", + "Name": "sayHi", + "ResourceName": "UnbrandedTypeSpec", + "Description": "Return hi", + "Parameters": [ + { + "$id": "137", + "Name": "unbrandedTypeSpecUrl", + "NameInRequest": "unbrandedTypeSpecUrl", + "Type": { + "$id": "138", + "Kind": "Primitive", + "Name": "Uri", + "IsNullable": false + }, + "Location": "Uri", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": true, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Client" + }, + { + "$id": "139", + "Name": "headParameter", + "NameInRequest": "head-parameter", + "Type": { + "$id": "140", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "141", + "Name": "queryParameter", + "NameInRequest": "queryParameter", + "Type": { + "$id": "142", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Query", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "143", + "Name": "optionalQuery", + "NameInRequest": "optionalQuery", + "Type": { + "$id": "144", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Query", + "IsRequired": false, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "145", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "146", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "147", + "Type": { + "$ref": "146" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "148", + "StatusCodes": [200], + "BodyType": { + "$ref": "38" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "GET", + "RequestBodyMediaType": "None", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/hello", + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": false + }, + { + "$id": "149", + "Name": "helloAgain", + "ResourceName": "UnbrandedTypeSpec", + "Description": "Return hi again", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "150", + "Name": "p1", + "NameInRequest": "p1", + "Type": { + "$id": "151", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "152", + "Name": "contentType", + "NameInRequest": "content-type", + "Type": { + "$id": "153", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$id": "154", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Value": "text/plain", + "IsNullable": false + }, + "Location": "Header", + "DefaultValue": { + "$id": "155", + "Type": { + "$ref": "153" + }, + "Value": "text/plain" + }, + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant" + }, + { + "$id": "156", + "Name": "p2", + "NameInRequest": "p2", + "Type": { + "$id": "157", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Path", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "158", + "Name": "action", + "NameInRequest": "action", + "Type": { + "$ref": "73" + }, + "Location": "Body", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "159", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "160", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "161", + "Type": { + "$ref": "160" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "162", + "StatusCodes": [200], + "BodyType": { + "$ref": "73" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "GET", + "RequestBodyMediaType": "Json", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/againHi/{p2}", + "RequestMediaTypes": ["text/plain"], + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": true + }, + { + "$id": "163", + "Name": "noContentType", + "ResourceName": "UnbrandedTypeSpec", + "Description": "Return hi again", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "164", + "Name": "p1", + "NameInRequest": "p1", + "Type": { + "$id": "165", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "166", + "Name": "p2", + "NameInRequest": "p2", + "Type": { + "$id": "167", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Path", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "168", + "Name": "action", + "NameInRequest": "action", + "Type": { + "$ref": "73" + }, + "Location": "Body", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "169", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "170", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "171", + "Type": { + "$ref": "170" + }, + "Value": "application/json" + } + }, + { + "$id": "172", + "Name": "contentType", + "NameInRequest": "Content-Type", + "Type": { + "$id": "173", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": true, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "174", + "Type": { + "$ref": "173" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "175", + "StatusCodes": [200], + "BodyType": { + "$ref": "73" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "GET", + "RequestBodyMediaType": "Json", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/noContentType/{p2}", + "RequestMediaTypes": ["application/json"], + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": false + }, + { + "$id": "176", + "Name": "helloDemo2", + "ResourceName": "UnbrandedTypeSpec", + "Description": "Return hi in demo2", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "177", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "178", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "179", + "Type": { + "$ref": "178" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "180", + "StatusCodes": [200], + "BodyType": { + "$ref": "38" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "GET", + "RequestBodyMediaType": "None", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/demoHi", + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": true + }, + { + "$id": "181", + "Name": "createLiteral", + "ResourceName": "UnbrandedTypeSpec", + "Description": "Create with literal value", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "182", + "Name": "body", + "NameInRequest": "body", + "Type": { + "$ref": "38" + }, + "Location": "Body", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "183", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "184", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "185", + "Type": { + "$ref": "184" + }, + "Value": "application/json" + } + }, + { + "$id": "186", + "Name": "contentType", + "NameInRequest": "Content-Type", + "Type": { + "$id": "187", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": true, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "188", + "Type": { + "$ref": "187" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "189", + "StatusCodes": [200], + "BodyType": { + "$ref": "38" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "POST", + "RequestBodyMediaType": "Json", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/literal", + "RequestMediaTypes": ["application/json"], + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": true + }, + { + "$id": "190", + "Name": "helloLiteral", + "ResourceName": "UnbrandedTypeSpec", + "Description": "Send literal parameters", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "191", + "Name": "p1", + "NameInRequest": "p1", + "Type": { + "$id": "192", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$id": "193", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Value": "test", + "IsNullable": false + }, + "Location": "Header", + "DefaultValue": { + "$id": "194", + "Type": { + "$ref": "192" + }, + "Value": "test" + }, + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant" + }, + { + "$id": "195", + "Name": "p2", + "NameInRequest": "p2", + "Type": { + "$id": "196", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$id": "197", + "Kind": "Primitive", + "Name": "Int32", + "IsNullable": false + }, + "Value": 123, + "IsNullable": false + }, + "Location": "Path", + "DefaultValue": { + "$id": "198", + "Type": { + "$ref": "196" + }, + "Value": 123 + }, + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant" + }, + { + "$id": "199", + "Name": "p3", + "NameInRequest": "p3", + "Type": { + "$id": "200", + "Kind": "Literal", + "Name": "Literal", + "LiteralValueType": { + "$id": "201", + "Kind": "Primitive", + "Name": "Boolean", + "IsNullable": false + }, + "Value": true, + "IsNullable": false + }, + "Location": "Query", + "DefaultValue": { + "$id": "202", + "Type": { + "$ref": "200" + }, + "Value": true + }, + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant" + }, + { + "$id": "203", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "204", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "205", + "Type": { + "$ref": "204" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "206", + "StatusCodes": [200], + "BodyType": { + "$ref": "38" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "GET", + "RequestBodyMediaType": "None", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/helloLiteral/{p2}", + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": true + }, + { + "$id": "207", + "Name": "topAction", + "ResourceName": "UnbrandedTypeSpec", + "Description": "top level method", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "208", + "Name": "action", + "NameInRequest": "action", + "Type": { + "$id": "209", + "Kind": "Primitive", + "Name": "DateTime", + "IsNullable": false + }, + "Location": "Path", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "210", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "211", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "212", + "Type": { + "$ref": "211" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "213", + "StatusCodes": [200], + "BodyType": { + "$ref": "38" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "GET", + "RequestBodyMediaType": "None", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/top/{action}", + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": true + }, + { + "$id": "214", + "Name": "topAction2", + "ResourceName": "UnbrandedTypeSpec", + "Description": "top level method2", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "215", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "216", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "217", + "Type": { + "$ref": "216" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "218", + "StatusCodes": [200], + "BodyType": { + "$ref": "38" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "GET", + "RequestBodyMediaType": "None", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/top2", + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": false + }, + { + "$id": "219", + "Name": "patchAction", + "ResourceName": "UnbrandedTypeSpec", + "Description": "top level patch", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "220", + "Name": "body", + "NameInRequest": "body", + "Type": { + "$ref": "38" + }, + "Location": "Body", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "221", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "222", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "223", + "Type": { + "$ref": "222" + }, + "Value": "application/json" + } + }, + { + "$id": "224", + "Name": "contentType", + "NameInRequest": "Content-Type", + "Type": { + "$id": "225", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": true, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "226", + "Type": { + "$ref": "225" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "227", + "StatusCodes": [200], + "BodyType": { + "$ref": "38" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "PATCH", + "RequestBodyMediaType": "Json", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/patch", + "RequestMediaTypes": ["application/json"], + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": false + }, + { + "$id": "228", + "Name": "anonymousBody", + "ResourceName": "UnbrandedTypeSpec", + "Description": "body parameter without body decorator", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "229", + "Name": "Thing", + "NameInRequest": "Thing", + "Description": "A model with a few properties of literal types", + "Type": { + "$ref": "38" + }, + "Location": "Body", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "230", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "231", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "232", + "Type": { + "$ref": "231" + }, + "Value": "application/json" + } + }, + { + "$id": "233", + "Name": "contentType", + "NameInRequest": "Content-Type", + "Type": { + "$id": "234", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": true, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "235", + "Type": { + "$ref": "234" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "236", + "StatusCodes": [200], + "BodyType": { + "$ref": "38" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "POST", + "RequestBodyMediaType": "Json", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/anonymousBody", + "RequestMediaTypes": ["application/json"], + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": true + }, + { + "$id": "237", + "Name": "friendlyModel", + "ResourceName": "UnbrandedTypeSpec", + "Description": "Model can have its friendly name", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "238", + "Name": "Friend", + "NameInRequest": "NotFriend", + "Description": "this is not a friendly model but with a friendly name", + "Type": { + "$ref": "123" + }, + "Location": "Body", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "239", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "240", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "241", + "Type": { + "$ref": "240" + }, + "Value": "application/json" + } + }, + { + "$id": "242", + "Name": "contentType", + "NameInRequest": "Content-Type", + "Type": { + "$id": "243", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": true, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "244", + "Type": { + "$ref": "243" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "245", + "StatusCodes": [200], + "BodyType": { + "$ref": "123" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "POST", + "RequestBodyMediaType": "Json", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/friendlyName", + "RequestMediaTypes": ["application/json"], + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": true + }, + { + "$id": "246", + "Name": "addTimeHeader", + "ResourceName": "UnbrandedTypeSpec", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "247", + "Name": "repeatabilityFirstSent", + "NameInRequest": "Repeatability-First-Sent", + "Type": { + "$id": "248", + "Kind": "Primitive", + "Name": "DateTime", + "IsNullable": false + }, + "Location": "Header", + "IsRequired": false, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "249", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "250", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "251", + "Type": { + "$ref": "250" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "252", + "StatusCodes": [204], + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false + } + ], + "HttpMethod": "GET", + "RequestBodyMediaType": "None", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/", + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": false + }, + { + "$id": "253", + "Name": "stringFormat", + "ResourceName": "UnbrandedTypeSpec", + "Description": "parameter has string format.", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "254", + "Name": "subscriptionId", + "NameInRequest": "subscriptionId", + "Type": { + "$id": "255", + "Kind": "Primitive", + "Name": "Guid", + "IsNullable": false + }, + "Location": "Path", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "256", + "Name": "body", + "NameInRequest": "body", + "Type": { + "$ref": "126" + }, + "Location": "Body", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "257", + "Name": "contentType", + "NameInRequest": "Content-Type", + "Type": { + "$id": "258", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": true, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "259", + "Type": { + "$ref": "258" + }, + "Value": "application/json" + } + }, + { + "$id": "260", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "261", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "262", + "Type": { + "$ref": "261" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "263", + "StatusCodes": [204], + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false + } + ], + "HttpMethod": "POST", + "RequestBodyMediaType": "Json", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/stringFormat/{subscriptionId}", + "RequestMediaTypes": ["application/json"], + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": true + }, + { + "$id": "264", + "Name": "projectedNameModel", + "ResourceName": "UnbrandedTypeSpec", + "Description": "Model can have its projected name", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "265", + "Name": "ProjectedModel", + "NameInRequest": "ModelWithProjectedName", + "Description": "this is a model with a projected name", + "Type": { + "$ref": "131" + }, + "Location": "Body", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "266", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "267", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "268", + "Type": { + "$ref": "267" + }, + "Value": "application/json" + } + }, + { + "$id": "269", + "Name": "contentType", + "NameInRequest": "Content-Type", + "Type": { + "$id": "270", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": true, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "271", + "Type": { + "$ref": "270" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "272", + "StatusCodes": [200], + "BodyType": { + "$ref": "131" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "POST", + "RequestBodyMediaType": "Json", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/projectedName", + "RequestMediaTypes": ["application/json"], + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": true + }, + { + "$id": "273", + "Name": "returnsAnonymousModel", + "ResourceName": "UnbrandedTypeSpec", + "Description": "return anonymous model", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "274", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "275", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "276", + "Type": { + "$ref": "275" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "277", + "StatusCodes": [200], + "BodyType": { + "$ref": "134" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "POST", + "RequestBodyMediaType": "None", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/returnsAnonymousModel", + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": true + }, + { + "$id": "278", + "Name": "getUnknownValue", + "ResourceName": "UnbrandedTypeSpec", + "Description": "get extensible enum", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "279", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "280", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "281", + "Type": { + "$ref": "280" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "282", + "StatusCodes": [200], + "BodyType": { + "$id": "283", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "GET", + "RequestBodyMediaType": "None", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/unknown-value", + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": false + }, + { + "$id": "284", + "Name": "internalProtocol", + "ResourceName": "UnbrandedTypeSpec", + "Description": "When set protocol false and convenient true, then the protocol method should be internal", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "285", + "Name": "body", + "NameInRequest": "body", + "Type": { + "$ref": "38" + }, + "Location": "Body", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "286", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "287", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "288", + "Type": { + "$ref": "287" + }, + "Value": "application/json" + } + }, + { + "$id": "289", + "Name": "contentType", + "NameInRequest": "Content-Type", + "Type": { + "$id": "290", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": true, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "291", + "Type": { + "$ref": "290" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "292", + "StatusCodes": [200], + "BodyType": { + "$ref": "38" + }, + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false, + "ContentTypes": ["application/json"] + } + ], + "HttpMethod": "POST", + "RequestBodyMediaType": "Json", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/internalProtocol", + "RequestMediaTypes": ["application/json"], + "BufferResponse": true, + "GenerateProtocolMethod": false, + "GenerateConvenienceMethod": true + }, + { + "$id": "293", + "Name": "stillConvenient", + "ResourceName": "UnbrandedTypeSpec", + "Description": "When set protocol false and convenient true, the convenient method should be generated even it has the same signature as protocol one", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "294", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "295", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "296", + "Type": { + "$ref": "295" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "297", + "StatusCodes": [204], + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false + } + ], + "HttpMethod": "GET", + "RequestBodyMediaType": "None", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/stillConvenient", + "BufferResponse": true, + "GenerateProtocolMethod": false, + "GenerateConvenienceMethod": true + }, + { + "$id": "298", + "Name": "headAsBoolean", + "ResourceName": "UnbrandedTypeSpec", + "Description": "head as boolean.", + "Parameters": [ + { + "$ref": "137" + }, + { + "$id": "299", + "Name": "id", + "NameInRequest": "id", + "Type": { + "$id": "300", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Path", + "IsRequired": true, + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Method" + }, + { + "$id": "301", + "Name": "accept", + "NameInRequest": "Accept", + "Type": { + "$id": "302", + "Kind": "Primitive", + "Name": "String", + "IsNullable": false + }, + "Location": "Header", + "IsApiVersion": false, + "IsResourceParameter": false, + "IsContentType": false, + "IsRequired": true, + "IsEndpoint": false, + "SkipUrlEncoding": false, + "Explode": false, + "Kind": "Constant", + "DefaultValue": { + "$id": "303", + "Type": { + "$ref": "302" + }, + "Value": "application/json" + } + } + ], + "Responses": [ + { + "$id": "304", + "StatusCodes": [204], + "BodyMediaType": "Json", + "Headers": [], + "IsErrorResponse": false + } + ], + "HttpMethod": "HEAD", + "RequestBodyMediaType": "None", + "Uri": "{unbrandedTypeSpecUrl}", + "Path": "/headAsBoolean/{id}", + "BufferResponse": true, + "GenerateProtocolMethod": true, + "GenerateConvenienceMethod": true + } + ], + "Protocol": { + "$id": "305" + }, + "Creatable": true + } + ], + "Auth": { + "$id": "306", + "ApiKey": { + "$id": "307", + "Name": "my-api-key" + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/tspconfig.yaml b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/tspconfig.yaml new file mode 100644 index 0000000000..26f2148ad7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel.TestProjects/Unbranded-TypeSpec/tspconfig.yaml @@ -0,0 +1,6 @@ +options: + "@azure-tools/typespec-csharp": + generate-convenience-methods: false + deserialize-null-collection-as-null-value: true + head-as-boolean: true + generate-test-project: true diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/ClientModelCodeWriterExtensionMethods.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/ClientModelCodeWriterExtensionMethods.cs new file mode 100644 index 0000000000..8bb63de910 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/ClientModelCodeWriterExtensionMethods.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.ClientModel +{ + public class ClientModelCodeWriterExtensionMethods : CodeWriterExtensionMethods + { + public override void WriteMethod(CodeWriter writer, Method method) + { + writer.Append($"// Sample plugin implementation of WriteMethod from {GetType()}"); + writer.WriteLine(); + base.WriteMethod(writer, method); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/ClientModelPlugin.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/ClientModelPlugin.cs new file mode 100644 index 0000000000..e2842a982c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/ClientModelPlugin.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ComponentModel.Composition; +using Microsoft.Generator.CSharp.ClientModel.Expressions; +using Microsoft.Generator.CSharp.ClientModel.Output; +using Microsoft.Generator.CSharp.ClientModel.Writers; +using Microsoft.Generator.CSharp.Expressions; +using Microsoft.Generator.CSharp.Input; +using Microsoft.Generator.CSharp.Writers; + +namespace Microsoft.Generator.CSharp.ClientModel +{ + public class ClientModelPlugin : CodeModelPlugin + { + private static ClientModelPlugin? _instance; + internal static ClientModelPlugin Instance => _instance ?? throw new InvalidOperationException("ClientModelPlugin is not loaded."); + public override ApiTypes ApiTypes { get; } + public override CodeWriterExtensionMethods CodeWriterExtensionMethods { get; } + + public override OutputLibrary GetOutputLibrary(InputNamespace input) => new ScmOutputLibrary(input); + + public override ExpressionTypeProviderWriter GetExpressionTypeProviderWriter(CodeWriter writer, ModelTypeProvider model) => new ScmExpressionTypeProviderWriter(writer, model); + + public override TypeFactory TypeFactory { get; } + + public override ExtensibleSnippets ExtensibleSnippets { get; } + + [ImportingConstructor] + public ClientModelPlugin(GeneratorContext context) + : base(context) + { + TypeFactory = new GeneratorCSharpTypeFactory(); + ExtensibleSnippets = new SystemExtensibleSnippets(); + ApiTypes = new SystemApiTypes(); + CodeWriterExtensionMethods = new ClientModelCodeWriterExtensionMethods(); + _instance = this; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/MessagePipelineExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/MessagePipelineExpression.cs new file mode 100644 index 0000000000..2cd1007154 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/MessagePipelineExpression.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives.Pipeline; +using System.Collections.Generic; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal sealed record MessagePipelineExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public PipelineMessageExpression CreateMessage(RequestOptionsExpression requestContext, ValueExpression responseClassifier) => new(Invoke(nameof(MessagePipeline.CreateMessage), requestContext, responseClassifier)); + public PipelineResponseExpression ProcessMessage(TypedValueExpression message, RequestOptionsExpression? requestContext, CancellationTokenExpression? cancellationToken, bool async) + { + var arguments = new List + { + Untyped, + message, + requestContext ?? Snippets.Null + }; + + if (cancellationToken != null) + { + arguments.Add(cancellationToken); + } + + return ClientPipelineExtensionsProvider.Instance.ProcessMessage(arguments, async); + } + + public ResultExpression ProcessHeadAsBoolMessage(TypedValueExpression message, RequestOptionsExpression? requestContext, bool async) + { + var arguments = new List + { + Untyped, + message, + requestContext ?? Snippets.Null + }; + + return ClientPipelineExtensionsProvider.Instance.ProcessHeadAsBoolMessage(arguments, async); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/PipelineMessageExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/PipelineMessageExpression.cs new file mode 100644 index 0000000000..c25e460a32 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/PipelineMessageExpression.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal sealed record PipelineMessageExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public PipelineRequestExpression Request => new(Property(nameof(PipelineMessage.Request))); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/PipelineRequestExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/PipelineRequestExpression.cs new file mode 100644 index 0000000000..3adf3f8f9f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/PipelineRequestExpression.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel.Primitives; +using Microsoft.Generator.CSharp.Expressions; +using static Microsoft.Generator.CSharp.Expressions.Snippets; + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal sealed record PipelineRequestExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public TypedValueExpression Uri => new FrameworkTypeExpression(typeof(Uri), Property(nameof(PipelineRequest.Uri))); + public RequestBodyExpression Content => new(Property(nameof(PipelineRequest.Content))); + public MethodBodyStatement SetMethod(string method) => new InvokeInstanceMethodStatement(Untyped, nameof(PipelineRequest.SetMethod), Literal(method)); + public MethodBodyStatement SetHeaderValue(string name, StringExpression value) => new InvokeInstanceMethodStatement(Untyped, nameof(PipelineRequest.SetHeaderValue), Literal(name), value); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/PipelineResponseExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/PipelineResponseExpression.cs new file mode 100644 index 0000000000..e0e2f65a97 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/PipelineResponseExpression.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal sealed record PipelineResponseExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public BinaryDataExpression Content => new(Property(nameof(PipelineResponse.Content))); + + public StreamExpression ContentStream => new(Property(nameof(PipelineResponse.ContentStream))); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/RequestBodyExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/RequestBodyExpression.cs new file mode 100644 index 0000000000..1381d2b4f7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/RequestBodyExpression.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal sealed record RequestBodyExpression(ValueExpression Untyped) : TypedValueExpression(Untyped); +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/RequestOptionsExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/RequestOptionsExpression.cs new file mode 100644 index 0000000000..ff0e267439 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/RequestOptionsExpression.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel; +using Microsoft.Generator.CSharp.Expressions; + + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal sealed record RequestOptionsExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public static RequestOptionsExpression FromCancellationToken() + => new(new InvokeStaticMethodExpression(null, nameof(FromCancellationToken), new ValueExpression[] { KnownParameters.CancellationTokenParameter })); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/ResultExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/ResultExpression.cs new file mode 100644 index 0000000000..14822d6288 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/ResultExpression.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal sealed record ResultExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public ValueExpression Value => Property(nameof(Result.Value)); + public BinaryDataExpression Content => throw new InvalidOperationException("Result does not have a Content property"); + public StreamExpression ContentStream => throw new InvalidOperationException("Result does not have a ContentStream property"); + + public static ResultExpression FromResponse(PipelineResponseExpression response) + => new(InvokeStatic(nameof(Result.FromResponse), response)); + + public static ResultExpression FromValue(ValueExpression value, PipelineResponseExpression response) + => new(InvokeStatic(nameof(Result.FromValue), value, response)); + + public static ResultExpression FromValue(CSharpType explicitValueType, ValueExpression value, PipelineResponseExpression response) + => new(new InvokeStaticMethodExpression(typeof(Result), nameof(Result.FromValue), new[] { value, response }, new[] { explicitValueType })); + + public ResultExpression FromValue(ValueExpression value) + => new(new InvokeStaticMethodExpression(typeof(Result), nameof(Result.FromValue), new[] { value, this })); + + public ResultExpression FromValue(CSharpType explicitValueType, ValueExpression value) + => new(new InvokeStaticMethodExpression(typeof(Result), nameof(Result.FromValue), new[] { value, this }, new[] { explicitValueType })); + + public PipelineResponseExpression GetRawResponse() => new(Invoke(nameof(Result.GetRawResponse))); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.SystemJsonElementSnippets.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.SystemJsonElementSnippets.cs new file mode 100644 index 0000000000..db5bb9150d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.SystemJsonElementSnippets.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal partial class SystemExtensibleSnippets + { + private class SystemJsonElementSnippets : JsonElementSnippets + { + public override ValueExpression GetBytesFromBase64(JsonElementExpression element, string? format) => InvokeExtension(typeof(ModelSerializationExtensions), element, nameof(ModelSerializationExtensions.GetBytesFromBase64), Snippets.Literal(format)); + public override ValueExpression GetChar(JsonElementExpression element) => InvokeExtension(typeof(ModelSerializationExtensions), element, nameof(ModelSerializationExtensions.GetChar)); + public override ValueExpression GetDateTimeOffset(JsonElementExpression element, string? format) => InvokeExtension(typeof(ModelSerializationExtensions), element, nameof(ModelSerializationExtensions.GetDateTimeOffset), Snippets.Literal(format)); + public override ValueExpression GetObject(JsonElementExpression element) => InvokeExtension(typeof(ModelSerializationExtensions), element, nameof(ModelSerializationExtensions.GetObject)); + public override ValueExpression GetTimeSpan(JsonElementExpression element, string? format) => InvokeExtension(typeof(ModelSerializationExtensions), element, nameof(ModelSerializationExtensions.GetTimeSpan), Snippets.Literal(format)); + public override MethodBodyStatement ThrowNonNullablePropertyIsNull(JsonPropertyExpression property) + => new InvokeStaticMethodStatement(typeof(ModelSerializationExtensions), nameof(ModelSerializationExtensions.ThrowNonNullablePropertyIsNull), new[] { property }, CallAsExtension: true); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.SystemModelSnippets.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.SystemModelSnippets.cs new file mode 100644 index 0000000000..cce1ee629c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.SystemModelSnippets.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel.Internal; +using System.ClientModel.Primitives; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal partial class SystemExtensibleSnippets + { + internal class SystemModelSnippets : ModelSnippets + { + public override Method BuildConversionToRequestBodyMethod(MethodSignatureModifiers modifiers) + { + return new Method + ( + new MethodSignature(ClientModelPlugin.Instance.Configuration.ApiTypes.ToRequestContentName, null, $"Convert into a {nameof(Utf8JsonRequestBody)}.", modifiers, typeof(RequestBody), null, Array.Empty()), + new[] + { + Snippets.Extensible.RestOperations.DeclareContentWithUtf8JsonWriter(out var requestContent, out var writer), + writer.WriteObjectValue(Snippets.This), + Snippets.Return(requestContent) + }, + "default" + ); + } + + public override Method BuildFromOperationResponseMethod(TypeProvider typeProvider, MethodSignatureModifiers modifiers) + { + var result = new Parameter("response", $"The result to deserialize the model from.", typeof(PipelineResponse), null, ValidationType.None, null); + return new Method + ( + new MethodSignature(ClientModelPlugin.Instance.Configuration.ApiTypes.FromResponseName, null, $"Deserializes the model from a raw response.", modifiers, typeProvider.Type, null, new[] { result }), + new MethodBodyStatement[] + { + Snippets.UsingVar("document", JsonDocumentExpression.Parse(new PipelineResponseExpression(result).Content), out var document), + Snippets.Return(ObjectTypeExpression.Deserialize(typeProvider, document.RootElement)) + }, + "default" + ); + } + + public override TypedValueExpression InvokeToRequestBodyMethod(TypedValueExpression model) => new RequestBodyExpression(model.Invoke("ToRequestBody")); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.SystemRestOperationsSnippets.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.SystemRestOperationsSnippets.cs new file mode 100644 index 0000000000..ff73cced5a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.SystemRestOperationsSnippets.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel; +using System.ClientModel.Internal; +using System.ClientModel.Primitives; +using System.Linq; +using Microsoft.Generator.CSharp.Expressions; +using Microsoft.Generator.CSharp.Input; + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal partial class SystemExtensibleSnippets + { + private class SystemRestOperationsSnippets : RestOperationsSnippets + { + public override StreamExpression GetContentStream(TypedValueExpression result) + => new ResultExpression(result).GetRawResponse().ContentStream; + + public override TypedValueExpression GetTypedResponseFromValue(TypedValueExpression value, TypedValueExpression result) + { + return ResultExpression.FromValue(value, GetRawResponse(result)); + } + + public override TypedValueExpression GetTypedResponseFromModel(TypeProvider typeProvider, TypedValueExpression result) + { + var response = GetRawResponse(result); + var model = new InvokeStaticMethodExpression(typeProvider.Type, ClientModelPlugin.Instance.Configuration.ApiTypes.FromResponseName, new[] { response }); + return ResultExpression.FromValue(model, response); + } + + public override TypedValueExpression GetTypedResponseFromEnum(EnumType enumType, TypedValueExpression result) + { + var response = GetRawResponse(result); + return ResultExpression.FromValue(EnumExpression.ToEnum(enumType, response.Content.ToObjectFromJson(typeof(string))), response); + } + + public override TypedValueExpression GetTypedResponseFromBinaryData(Type responseType, TypedValueExpression result, string? contentType = null) + { + var rawResponse = GetRawResponse(result); + if (responseType == typeof(string) && contentType != null && FormattableStringHelpers.ToMediaType(contentType) == BodyMediaType.Text) + { + return ResultExpression.FromValue(rawResponse.Content.InvokeToString(), rawResponse); + } + return responseType == typeof(BinaryData) + ? ResultExpression.FromValue(rawResponse.Content, rawResponse) + : ResultExpression.FromValue(rawResponse.Content.ToObjectFromJson(responseType), rawResponse); + } + + public override MethodBodyStatement DeclareHttpMessage(MethodSignatureBase createRequestMethodSignature, out TypedValueExpression message) + { + var messageVar = new VariableReference(typeof(PipelineMessage), "message"); + message = messageVar; + return Snippets.UsingDeclare(messageVar, new InvokeInstanceMethodExpression(null, createRequestMethodSignature.Name, createRequestMethodSignature.Parameters.Select(p => (ValueExpression)p).ToList(), null, false)); + } + + public override MethodBodyStatement DeclareContentWithUtf8JsonWriter(out TypedValueExpression content, out Utf8JsonWriterExpression writer) + { + var contentVar = new VariableReference(typeof(Utf8JsonRequestBody), "content"); + content = contentVar; + writer = new Utf8JsonRequestBodyExpression(content).JsonWriter; + return Snippets.Var(contentVar, Snippets.New.Instance(typeof(Utf8JsonRequestBody))); + } + + public override MethodBodyStatement DeclareContentWithXmlWriter(out TypedValueExpression content, out XmlWriterExpression writer) + { + throw new NotImplementedException("Xml serialization isn't supported in System.Net.ClientModel yet"); + } + + public override MethodBodyStatement InvokeServiceOperationCallAndReturnHeadAsBool(TypedValueExpression pipeline, TypedValueExpression message, TypedValueExpression clientDiagnostics, bool async) + { + var resultVar = new VariableReference(typeof(NullableResult), "result"); + var result = new ResultExpression(resultVar); + return new MethodBodyStatement[] + { + Snippets.Var(resultVar, new MessagePipelineExpression(pipeline).ProcessHeadAsBoolMessage(message, new RequestOptionsExpression(KnownParameters.RequestContext), async)), + Snippets.Return(ResultExpression.FromValue(result.Value, result.GetRawResponse())) + }; + } + + public override TypedValueExpression InvokeServiceOperationCall(TypedValueExpression pipeline, TypedValueExpression message, bool async) + => ResultExpression.FromResponse(new MessagePipelineExpression(pipeline).ProcessMessage(message, new RequestOptionsExpression(KnownParameters.RequestContext), null, async)); + + private static PipelineResponseExpression GetRawResponse(TypedValueExpression result) + => result.Type.Equals(typeof(PipelineResponse)) + ? new PipelineResponseExpression(result) + : new ResultExpression(result).GetRawResponse(); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.cs new file mode 100644 index 0000000000..7f223637de --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/SystemExtensibleSnippets.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal partial class SystemExtensibleSnippets : ExtensibleSnippets + { + public override JsonElementSnippets JsonElement { get; } = new SystemJsonElementSnippets(); + public override XElementSnippets XElement => throw new NotImplementedException("XElement extensions aren't supported in ClientModel plugin yet."); + public override XmlWriterSnippets XmlWriter => throw new NotImplementedException("XmlWriter extensions aren't supported in ClientModel plugin yet."); + public override RestOperationsSnippets RestOperations { get; } = new SystemRestOperationsSnippets(); + public override ModelSnippets Model { get; } = new SystemModelSnippets(); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/Utf8JsonRequestBodyExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/Utf8JsonRequestBodyExpression.cs new file mode 100644 index 0000000000..d1cd698447 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Expressions/Utf8JsonRequestBodyExpression.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Internal; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.ClientModel.Expressions +{ + internal sealed record Utf8JsonRequestBodyExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public Utf8JsonWriterExpression JsonWriter => new(Property(nameof(Utf8JsonRequestBody.JsonWriter))); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/FormattableStringHelpers.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/FormattableStringHelpers.cs new file mode 100644 index 0000000000..0b604d713b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/FormattableStringHelpers.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Generator.CSharp.Input; +using System.Text.RegularExpressions; + +namespace Microsoft.Generator.CSharp.ClientModel +{ + internal static class FormattableStringHelpers + { + private static readonly Regex ContentTypeRegex = new Regex(@"(application|audio|font|example|image|message|model|multipart|text|video|x-(?:[0-9A-Za-z!#$%&'*+.^_`|~-]+))\/([0-9A-Za-z!#$%&'*.^_`|~-]+)\s*(?:\+([0-9A-Za-z!#$%&'*.^_`|~-]+))?\s*(?:;.\s*(\S*))?", RegexOptions.Compiled); + + public static BodyMediaType ToMediaType(string contentType) + { + var matches = ContentTypeRegex.Matches(contentType); + if (matches.Count == 0) + { + throw new NotSupportedException($"Content type {contentType} is not supported."); + } + + var type = matches[0].Groups[1].Value; + var subType = matches[0].Groups[2].Value; + var suffix = matches[0].Groups[3].Value; + var parameter = matches[0].Groups[4].Value; + + var typeSubs = contentType.Split('/'); + if (typeSubs.Length != 2) + { + throw new NotSupportedException($"Content type {contentType} is not supported."); + } + + if ((subType == "json" || suffix == "json") && (type == "application" || type == "text") && suffix == "" && parameter == "") + { + return BodyMediaType.Json; + } + + if ((subType == "xml" || suffix == "xml") && (type == "application" || type == "text")) + { + return BodyMediaType.Xml; + } + + if (type == "audio" || type == "image" || type == "video" || subType == "octet-stream" || parameter == "serialization=Avro") + { + return BodyMediaType.Binary; + } + + if (type == "application" && subType == "formEncoded") + { + return BodyMediaType.Form; + } + + if (type == "multipart" && subType == "form-data") + { + return BodyMediaType.Multipart; + } + + if (type == "application") + { + return BodyMediaType.Binary; + } + + if (type == "text") + { + return BodyMediaType.Text; + } + + throw new NotSupportedException($"Content type {contentType} is not supported."); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/GeneratorCSharpTypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/GeneratorCSharpTypeFactory.cs new file mode 100644 index 0000000000..26ba3a03cc --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/GeneratorCSharpTypeFactory.cs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.Generator.CSharp.Expressions; +using Microsoft.Generator.CSharp.Input; +using System.Net.Http; +using System.Net.Http.Headers; + +namespace Microsoft.Generator.CSharp.ClientModel +{ + internal class GeneratorCSharpTypeFactory : TypeFactory + { + /// + /// This method will attempt to retrieve the of the input type. + /// + /// The input type to convert. + /// The of the input type. + public override CSharpType CreateType(InputType inputType) => inputType switch + { + InputLiteralType literalType => CSharpType.FromLiteral(CreateType(literalType.LiteralValueType), literalType.Value), + InputUnionType unionType => CSharpType.FromUnion(unionType.UnionItemTypes.Select(CreateType).ToArray(), unionType.IsNullable), + InputList { IsEmbeddingsVector: true } listType => new CSharpType(typeof(ReadOnlyMemory<>), listType.IsNullable, CreateType(listType.ElementType)), + InputList listType => new CSharpType(typeof(IList<>), listType.IsNullable, CreateType(listType.ElementType)), + InputDictionary dictionaryType => new CSharpType(typeof(IDictionary<,>), inputType.IsNullable, typeof(string), CreateType(dictionaryType.ValueType)), + // TODO ---- Add when the ResolveEnum and ResolveModel implementation is available. + // InputEnumType enumType => _library.ResolveEnum(enumType).WithNullable(inputType.IsNullable), + // InputModelType model => _library.ResolveModel(model).WithNullable(inputType.IsNullable), + InputPrimitiveType primitiveType => primitiveType.Kind switch + { + InputTypeKind.BinaryData => new CSharpType(typeof(BinaryData), inputType.IsNullable), + InputTypeKind.Boolean => new CSharpType(typeof(bool), inputType.IsNullable), + InputTypeKind.ContentType => new CSharpType(typeof(HttpContent), inputType.IsNullable), + InputTypeKind.Date => new CSharpType(typeof(DateTimeOffset), inputType.IsNullable), + InputTypeKind.DateTime => new CSharpType(typeof(DateTimeOffset), inputType.IsNullable), + InputTypeKind.DateTimeISO8601 => new CSharpType(typeof(DateTimeOffset), inputType.IsNullable), + InputTypeKind.DateTimeRFC1123 => new CSharpType(typeof(DateTimeOffset), inputType.IsNullable), + InputTypeKind.DateTimeRFC3339 => new CSharpType(typeof(DateTimeOffset), inputType.IsNullable), + InputTypeKind.DateTimeRFC7231 => new CSharpType(typeof(DateTimeOffset), inputType.IsNullable), + InputTypeKind.DateTimeUnix => new CSharpType(typeof(DateTimeOffset), inputType.IsNullable), + InputTypeKind.Decimal => new CSharpType(typeof(decimal), inputType.IsNullable), + InputTypeKind.Decimal128 => new CSharpType(typeof(decimal), inputType.IsNullable), + InputTypeKind.DurationISO8601 => new CSharpType(typeof(TimeSpan), inputType.IsNullable), + InputTypeKind.DurationSeconds => new CSharpType(typeof(TimeSpan), inputType.IsNullable), + InputTypeKind.DurationSecondsFloat => new CSharpType(typeof(TimeSpan), inputType.IsNullable), + InputTypeKind.DurationConstant => new CSharpType(typeof(TimeSpan), inputType.IsNullable), + InputTypeKind.ETag => new CSharpType(typeof(EntityTagHeaderValue), inputType.IsNullable), + InputTypeKind.Float32 => new CSharpType(typeof(float), inputType.IsNullable), + InputTypeKind.Float64 => new CSharpType(typeof(double), inputType.IsNullable), + InputTypeKind.Float128 => new CSharpType(typeof(decimal), inputType.IsNullable), + InputTypeKind.Guid => new CSharpType(typeof(Guid), inputType.IsNullable), + InputTypeKind.SByte => new CSharpType(typeof(sbyte), inputType.IsNullable), + InputTypeKind.Byte => new CSharpType(typeof(byte), inputType.IsNullable), + InputTypeKind.Int32 => new CSharpType(typeof(int), inputType.IsNullable), + InputTypeKind.Int64 => new CSharpType(typeof(long), inputType.IsNullable), + InputTypeKind.SafeInt => new CSharpType(typeof(long), inputType.IsNullable), + InputTypeKind.IPAddress => new CSharpType(typeof(IPAddress), inputType.IsNullable), + InputTypeKind.RequestMethod => new CSharpType(typeof(HttpMethod), inputType.IsNullable), + InputTypeKind.Stream => new CSharpType(typeof(Stream), inputType.IsNullable), + InputTypeKind.String => new CSharpType(typeof(string), inputType.IsNullable), + InputTypeKind.Time => new CSharpType(typeof(TimeSpan), inputType.IsNullable), + InputTypeKind.Uri => new CSharpType(typeof(Uri), inputType.IsNullable), + _ => new CSharpType(typeof(object), inputType.IsNullable), + }, + InputGenericType genericType => new CSharpType(genericType.Type, CreateType(genericType.ArgumentType)).WithNullable(inputType.IsNullable), + _ => throw new Exception("Unknown type") + }; + + public override Method CreateMethod(InputOperation operation, bool returnProtocol = true) + { + var methodType = GetMethodType(operation); + switch (methodType) + { + case "default": + return new Method + ( + new MethodSignature(operation.Name, $"{operation?.Summary}", null, MethodSignatureModifiers.Public, null, null, Array.Empty()), + Array.Empty(), + methodType + ); + case "longRunning": + return new Method + ( + CreateLongRunningMethodSignature(operation), + Array.Empty(), + methodType + ); + default: + throw new Exception($"Unknown method type {methodType}"); + } + } + + private static string GetMethodType(InputOperation operation) + { + var defaultMethodType = "default"; + + if (operation.LongRunning is not null) + return "longRunning"; + if (operation.Paging is not null) + return "paging"; + return defaultMethodType; + } + + private MethodSignature CreateLongRunningMethodSignature(InputOperation operation) + { + var methodName = operation.Name + "Async"; + var returnType = new CSharpType(typeof(Task<>), GetReturnType(operation).Arguments, false); + var parameters = operation.Parameters.Select(p => new Parameter(p.Name, $"{p.Description}", CreateType(p.Type), DefaultValue: null, ValidationType.None, Initializer: null)).ToList(); + return new MethodSignature(methodName, $"{operation?.Summary}", null, MethodSignatureModifiers.Public, returnType, null, parameters); + } + + private CSharpType GetReturnType(InputOperation operation) + { + CSharpType? responseType = null; + var firstResponse = operation.Responses.FirstOrDefault(); + if (firstResponse != null) + { + var bodyType = firstResponse.BodyType; + if (bodyType != null) + { + responseType = CreateType(bodyType); + } + } + + if (responseType is null) + { + responseType = new CSharpType(typeof(void), false); + } + + return responseType; + } + + public override CSharpType MatchConditionsType() + { + // TO-DO: Determine what the correct type is for MatchConditions: https://github.com/Azure/autorest.csharp/issues/4166 + throw new NotImplementedException(); + } + + public override CSharpType RequestConditionsType() + { + // TO-DO: Determine what the correct type is for RequestConditions: https://github.com/Azure/autorest.csharp/issues/4166 + throw new NotImplementedException(); + } + + public override CSharpType TokenCredentialType() + { + // TO-DO: Determine what the correct type is for TokenCredential: https://github.com/Azure/autorest.csharp/issues/4166 + throw new NotImplementedException(); + } + + public override CSharpType PageResponseType() + { + // TO-DO: Determine what the correct type is for Page: https://github.com/Azure/autorest.csharp/issues/4166 + throw new NotImplementedException(); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Microsoft.Generator.CSharp.ClientModel.csproj b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Microsoft.Generator.CSharp.ClientModel.csproj new file mode 100644 index 0000000000..718c62ec9e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Microsoft.Generator.CSharp.ClientModel.csproj @@ -0,0 +1,19 @@ + + + + Microsoft.Generator.CSharp.ClientModel + Exe + net8.0 + enable + 1.0.0-beta.1 + + + + + + + + + + + \ No newline at end of file diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Models/Types/ClientPipelineExtensionsProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Models/Types/ClientPipelineExtensionsProvider.cs new file mode 100644 index 0000000000..2f16cb24d0 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Models/Types/ClientPipelineExtensionsProvider.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Generator.CSharp.Expressions; +using System.ClientModel.Primitives.Pipeline; +using System.ClientModel.Primitives; +using System.ClientModel; +using Microsoft.Generator.CSharp.ClientModel.Expressions; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.ClientModel +{ + internal class ClientPipelineExtensionsProvider : TypeProvider + { + private static readonly Lazy _instance = new(() => new ClientPipelineExtensionsProvider()); + public static ClientPipelineExtensionsProvider Instance => _instance.Value; + public override string Name { get; } + + private const string _processMessageAsync = "ProcessMessageAsync"; + private const string _processMessage = "ProcessMessage"; + private const string _processHeadAsBoolMessageAsync = "ProcessHeadAsBoolMessageAsync"; + private const string _processHeadAsBoolMessage = "ProcessHeadAsBoolMessage"; + + private Parameter _pipelineParam; + private Parameter _messageParam; + private Parameter _requestContextParam; + private ParameterReference _pipeline; + private ParameterReference _message; + private ParameterReference _requestContext; + private MemberExpression _messageResponse; + + private ClientPipelineExtensionsProvider() + : base(null) + { + Name = "ClientPipelineExtensions"; + DeclarationModifiers = TypeSignatureModifiers.Internal | TypeSignatureModifiers.Static; + _pipelineParam = new Parameter("pipeline", null, typeof(Pipeline), null, ValidationType.None, null); + _messageParam = new Parameter("message", null, typeof(PipelineMessage), null, ValidationType.None, null); + _requestContextParam = new Parameter("requestContext", null, typeof(RequestOptions), null, ValidationType.None, null); + _pipeline = new ParameterReference(_pipelineParam); + _message = new ParameterReference(_messageParam); + _requestContext = new ParameterReference(_requestContextParam); + _messageResponse = new MemberExpression(_message, "Response"); + } + + internal PipelineResponseExpression ProcessMessage(IReadOnlyList arguments, bool isAsync) + { + return new(new InvokeStaticMethodExpression(Type, isAsync ? _processMessageAsync : _processMessage, arguments, CallAsExtension: true, CallAsAsync: isAsync)); + } + + internal ResultExpression ProcessHeadAsBoolMessage(IReadOnlyList arguments, bool isAsync) + { + return new(new InvokeStaticMethodExpression(Type, isAsync ? _processHeadAsBoolMessageAsync : _processHeadAsBoolMessage, arguments, CallAsExtension: true, CallAsAsync: isAsync)); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Output/ScmOutputLibrary.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Output/ScmOutputLibrary.cs new file mode 100644 index 0000000000..5eef562b8b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Output/ScmOutputLibrary.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.Generator.CSharp.Input; + +namespace Microsoft.Generator.CSharp.ClientModel.Output +{ + internal class ScmOutputLibrary : OutputLibrary + { + public ScmOutputLibrary(InputNamespace input) : base(input) + { + } + + protected override ModelTypeProvider[] BuildModels() + { + List modelProviders = new List(); + + foreach (var model in Input.Models) + { + modelProviders.Add(new ModelTypeProvider(model, null)); + } + + return modelProviders.ToArray(); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Program.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Program.cs new file mode 100644 index 0000000000..ecd85ed8ab --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Program.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; +using System.Threading.Tasks; +using Microsoft.Generator.CSharp.ClientModel.Expressions; + +namespace Microsoft.Generator.CSharp.ClientModel +{ + internal static class Program + { + public static int Main(string[] args) + { + //var outputPath = args[0]; + //var generatedPath = Path.Combine(outputPath, "src", "Generated"); + ////TODO need to load config / type factory + //Configuration? configuration = null; + //var generator = new CSharpGen(); + //var plugin = new ClientModelPlugin(new GeneratorContext(configuration!)); + + //await generator.ExecuteAsync(plugin); + return 0; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Properties/launchSettings.json b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Properties/launchSettings.json new file mode 100644 index 0000000000..12116140cc --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Unbranded-TypeSpec": { + "commandName": "Project", + "commandLineArgs": "$(SolutionDir)\\Microsoft.Generator.CSharp.ClientModel.TestProjects\\Unbranded-TypeSpec" + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/SystemApiTypes.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/SystemApiTypes.cs new file mode 100644 index 0000000000..6d98e521c7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/SystemApiTypes.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ClientModel; +using System.ClientModel.Internal; +using System.ClientModel.Primitives.Pipeline; +using System.ClientModel.Primitives; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.ClientModel +{ + /// + /// Represents API types as part of the namespace. + /// + internal class SystemApiTypes : ApiTypes + { + public override Type ChangeTrackingListType => typeof(OptionalList<>); + public override Type ChangeTrackingDictionaryType => typeof(OptionalDictionary<,>); + public override Type ResponseType => typeof(Result); + public override Type ResponseOfTType => typeof(Result<>); + public override string ResponseParameterName => "result"; + public override string ContentStreamName => $"{GetRawResponseName}().{nameof(PipelineResponse.ContentStream)}"; + public override string StatusName => $"{GetRawResponseName}().{nameof(PipelineResponse.Status)}"; + public override string GetRawResponseName => nameof(Result.GetRawResponse); + + public override Type HttpPipelineType => typeof(MessagePipeline); + public override Type PipelineExtensionsType => typeof(PipelineProtocolExtensions); + public override string HttpPipelineCreateMessageName => nameof(MessagePipeline.CreateMessage); + + public override Type HttpMessageType => typeof(PipelineMessage); + public override string HttpMessageResponseName => nameof(PipelineMessage.Response); + public override string HttpMessageResponseStatusName => nameof(PipelineResponse.Status); + + public override Type ClientDiagnosticsType => typeof(TelemetrySource); + public override string ClientDiagnosticsCreateScopeName => nameof(TelemetrySource.CreateSpan); + + public override Type ClientOptionsType => typeof(RequestOptions); + + public override Type RequestContextType => typeof(RequestOptions); + + public override Type BearerAuthenticationPolicyType => throw new NotSupportedException("Bearer authentication is not supported in non-branded libraries yet"); + public override Type KeyCredentialType => typeof(KeyCredential); + public override Type HttpPipelineBuilderType => typeof(MessagePipeline); + public override Type KeyCredentialPolicyType => typeof(KeyCredentialPolicy); + public override FormattableString GetHttpPipelineBearerString(string pipelineField, string optionsVariable, string credentialVariable, string scopesParamName) + => $"{pipelineField} = {HttpPipelineBuilderType}.Build({optionsVariable}, new {BearerAuthenticationPolicyType}({credentialVariable}, {new CodeWriterDeclaration(scopesParamName)}));"; + public override FormattableString GetHttpPipelineClassifierString(string pipelineField, string optionsVariable, FormattableString perCallPolicies, FormattableString perRetryPolicies) + => $"{pipelineField:I} = {typeof(MessagePipeline)}.{nameof(MessagePipeline.Create)}({optionsVariable:I}, {perRetryPolicies}, {perCallPolicies});"; + + public override Type HttpPipelinePolicyType => typeof(IPipelinePolicy); + + public override string HttpMessageRequestName => nameof(PipelineMessage.Request); + + public override FormattableString GetSetMethodString(string requestName, string method) + { + return $"{requestName}.{nameof(PipelineRequest.SetMethod)}(\"{method}\");"; + } + + private string GetHttpMethodName(string method) + { + return $"{method[0]}{method.Substring(1).ToLowerInvariant()}"; + } + + public override FormattableString GetSetUriString(string requestName, string uriName) + => $"{requestName}.Uri = {uriName}.{nameof(RequestUri.ToUri)}();"; + + public override FormattableString GetSetContentString(string requestName, string contentName) + => $"{requestName}.Content = {contentName};"; + + public override Type RequestUriType => typeof(RequestUri); + public override Type RequestContentType => typeof(RequestBody); + public override string CancellationTokenName => nameof(RequestOptions.CancellationToken); + public override string ToRequestContentName => "ToRequestBody"; + public override string RequestContentCreateName => nameof(RequestBody.CreateFromStream); + + public override Type IUtf8JsonSerializableType => typeof(IUtf8JsonWriteable); + + public override Type IXmlSerializableType => throw new NotSupportedException("Xml serialization is not supported in non-branded libraries yet"); + public override string IUtf8JsonSerializableWriteName => nameof(IUtf8JsonWriteable.Write); + + public override Type Utf8JsonWriterExtensionsType => typeof(ModelSerializationExtensions); + public override string Utf8JsonWriterExtensionsWriteObjectValueName => nameof(ModelSerializationExtensions.WriteObjectValue); + public override string Utf8JsonWriterExtensionsWriteNumberValueName => nameof(ModelSerializationExtensions.WriteNumberValue); + public override string Utf8JsonWriterExtensionsWriteStringValueName => nameof(ModelSerializationExtensions.WriteStringValue); + public override string Utf8JsonWriterExtensionsWriteBase64StringValueName => nameof(ModelSerializationExtensions.WriteBase64StringValue); + + public override Type OptionalType => typeof(OptionalProperty); + public override Type OptionalPropertyType => typeof(OptionalProperty<>); + + public override string OptionalIsCollectionDefinedName => nameof(OptionalProperty.IsCollectionDefined); + public override string OptionalIsDefinedName => nameof(OptionalProperty.IsDefined); + public override string OptionalToDictionaryName => nameof(OptionalProperty.ToDictionary); + public override string OptionalToListName => nameof(OptionalProperty.ToList); + public override string OptionalToNullableName => nameof(OptionalProperty.ToNullable); + + public override Type RequestFailedExceptionType => typeof(MessageFailedException); + + public override string ResponseClassifierIsErrorResponseName => nameof(ResponseErrorClassifier.IsErrorResponse); + + public override Type ResponseClassifierType => typeof(ResponseErrorClassifier); + public override Type StatusCodeClassifierType => typeof(StatusResponseClassifier); + + public override ValueExpression GetCreateFromStreamSampleExpression(ValueExpression freeFormObjectExpression) + => new InvokeStaticMethodExpression(ClientModelPlugin.Instance.Configuration.ApiTypes.RequestContentType, ClientModelPlugin.Instance.Configuration.ApiTypes.RequestContentCreateName, new[] { BinaryDataExpression.FromObjectAsJson(freeFormObjectExpression).ToStream() }); + + public override string EndPointSampleValue => "https://my-service.com"; + + public override string JsonElementVariableName => "element"; + + public override ValueExpression GetKeySampleExpression(string clientName) + => new InvokeStaticMethodExpression(typeof(Environment), nameof(Environment.GetEnvironmentVariable), new[] { new StringLiteralExpression($"{clientName}_KEY", false) }); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Writers/ScmExpressionTypeProviderWriter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Writers/ScmExpressionTypeProviderWriter.cs new file mode 100644 index 0000000000..a4b4004efa --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Writers/ScmExpressionTypeProviderWriter.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Writers; + +namespace Microsoft.Generator.CSharp.ClientModel.Writers +{ + internal class ScmExpressionTypeProviderWriter : ExpressionTypeProviderWriter + { + public ScmExpressionTypeProviderWriter(CodeWriter writer, ModelTypeProvider model) + : base(writer, model) + { + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenClientAttribute.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenClientAttribute.cs new file mode 100644 index 0000000000..219f49bcf5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenClientAttribute.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System; + +namespace Microsoft.Generator.CSharp.Customization +{ + [AttributeUsage(AttributeTargets.Class)] + public class CodeGenClientAttribute : CodeGenTypeAttribute + { + public Type? ParentClient { get; set; } + + public CodeGenClientAttribute(string originalName) : base(originalName) + { + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenMemberAttribute.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenMemberAttribute.cs new file mode 100644 index 0000000000..435268e9c5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenMemberAttribute.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System; + +namespace Microsoft.Generator.CSharp.Customization +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class CodeGenMemberAttribute : CodeGenTypeAttribute + { + public CodeGenMemberAttribute() : base(null) + { + } + + public CodeGenMemberAttribute(string originalName) : base(originalName) + { + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenModelAttribute.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenModelAttribute.cs new file mode 100644 index 0000000000..fd1e7d7cf9 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenModelAttribute.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System; + +namespace Microsoft.Generator.CSharp.Customization +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct)] + public class CodeGenModelAttribute : CodeGenTypeAttribute + { + /// + /// Gets or sets a coma separated list of additional model usage modes. Allowed values: model, error, intput, output. + /// + public string[]? Usage { get; set; } + + /// + /// Gets or sets a coma separated list of additional model serialization formats. + /// + public string[]? Formats { get; set; } + + public CodeGenModelAttribute() : base(null) + { + } + + public CodeGenModelAttribute(string originalName): base(originalName) + { + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenOverrideServiceVersionsAttribute.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenOverrideServiceVersionsAttribute.cs new file mode 100644 index 0000000000..16bf3f065c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenOverrideServiceVersionsAttribute.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System; + +namespace Microsoft.Generator.CSharp.Customization +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class CodeGenOverrideServiceVersionsAttribute : Attribute + { + public string[] Versions { get; } + + public CodeGenOverrideServiceVersionsAttribute(params string[] versions) + { + Versions = versions; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenSerializationAttribute.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenSerializationAttribute.cs new file mode 100644 index 0000000000..4525cca6a4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenSerializationAttribute.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System; + +namespace Microsoft.Generator.CSharp.Customization +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)] + public class CodeGenSerializationAttribute : Attribute + { + /// + /// Gets or sets the property name which these hooks should apply to + /// + public string? PropertyName { get; set; } + /// + /// Gets or sets the serialization path of the property in the JSON + /// + public string[]? SerializationPath { get; } + /// + /// Gets or sets the method name to use when serializing the property value (property name excluded) + /// The signature of the serialization hook method must be or compatible with when invoking: + /// private void SerializeHook(Utf8JsonWriter writer); + /// + public string? SerializationValueHook { get; set; } + /// + /// Gets or sets the method name to use when deserializing the property value from the JSON + /// private static void DeserializationHook(JsonProperty property, ref TypeOfTheProperty propertyValue); // if the property is required + /// private static void DeserializationHook(JsonProperty property, ref Optional<TypeOfTheProperty> propertyValue); // if the property is optional + /// + public string? DeserializationValueHook { get; set; } + /// + /// Gets or sets the method name to use when serializing the property value (property name excluded) + /// The signature of the serialization hook method must be or compatible with when invoking: + /// private void SerializeHook(StringBuilder builder); + /// + public string? BicepSerializationValueHook { get; set; } + + public CodeGenSerializationAttribute(string propertyName) + { + PropertyName = propertyName; + } + + public CodeGenSerializationAttribute(string propertyName, string serializationName) + { + PropertyName = propertyName; + SerializationPath = new[] { serializationName }; + } + + public CodeGenSerializationAttribute(string propertyName, string[] serializationPath) + { + PropertyName = propertyName; + SerializationPath = serializationPath; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenSuppressAttribute.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenSuppressAttribute.cs new file mode 100644 index 0000000000..96cef546e7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenSuppressAttribute.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Customization +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Struct, AllowMultiple = true)] + public class CodeGenSuppressAttribute : Attribute + { + public string Member { get; } + public Type[] Parameters { get; } + + public CodeGenSuppressAttribute(string member, params Type[] parameters) + { + Member = member; + Parameters = parameters; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenSuppressTypeAttribute.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenSuppressTypeAttribute.cs new file mode 100644 index 0000000000..d6d9142d66 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenSuppressTypeAttribute.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System; + +namespace Microsoft.Generator.CSharp.Customization +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class CodeGenSuppressTypeAttribute : Attribute + { + public string Typename { get; } + + public CodeGenSuppressTypeAttribute(string typename) + { + Typename = typename; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenTypeAttribute.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenTypeAttribute.cs new file mode 100644 index 0000000000..ec3ba01fe0 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/CodeGenTypeAttribute.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +using System; + +namespace Microsoft.Generator.CSharp.Customization +{ + [AttributeUsage(AttributeTargets.Class)] + public class CodeGenTypeAttribute : Attribute + { + public string? OriginalName { get; } + + public CodeGenTypeAttribute(string? originalName) + { + OriginalName = originalName; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/Microsoft.Generator.CSharp.Customization.csproj b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/Microsoft.Generator.CSharp.Customization.csproj new file mode 100644 index 0000000000..0672ddb0e2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Customization/src/Microsoft.Generator.CSharp.Customization.csproj @@ -0,0 +1,8 @@ + + + + net8.0 + enable + + + diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputLibrary.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputLibrary.cs new file mode 100644 index 0000000000..cb924a7fc3 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputLibrary.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; +using System; +using System.Threading.Tasks; + +namespace Microsoft.Generator.CSharp.Input +{ + public static class InputLibrary + { + private const string CodeModelInputFileName = "tspCodeModel.json"; + + public static async Task Load(string outputDirectory) + { + var codeModelFile = Path.Combine(outputDirectory, CodeModelInputFileName); + if (!File.Exists(codeModelFile)) + { + throw new InvalidOperationException($"File {codeModelFile} does not exist."); + } + + // Read and deserialize tspCodeModel.json + var json = await File.ReadAllTextAsync(codeModelFile); + return TypeSpecSerialization.Deserialize(json) ?? throw new InvalidOperationException($"Deserializing {codeModelFile} has failed."); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/BodyMediaType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/BodyMediaType.cs new file mode 100644 index 0000000000..7deaef61e5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/BodyMediaType.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public enum BodyMediaType + { + None, + Binary, + Form, + Json, + Multipart, + Text, + Xml + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/ExampleMockValueBuilder.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/ExampleMockValueBuilder.cs new file mode 100644 index 0000000000..aac235e2db --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/ExampleMockValueBuilder.cs @@ -0,0 +1,212 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Generator.CSharp.Input +{ + internal class ExampleMockValueBuilder + { + public const string ShortVersionMockExampleKey = "ShortVersion"; + public const string MockExampleAllParameterKey = "AllParameters"; + + // TO-DO: Remove dependency on Configuration class and replace with the actual mock endpoint value https://github.com/Azure/autorest.csharp/issues/4227 + private static readonly string EndpointMockValue = "mockValue"; // Configuration.Instance.ApiTypes.EndPointSampleValue; + + private static readonly ConcurrentDictionary _cache = new(); + + public static InputClientExample BuildClientExample(InputClient client, bool useAllParameters) + { + _cache.Clear(); + var clientParameterExamples = new List(); + foreach (var parameter in client.Parameters) + { + if (!useAllParameters && !parameter.IsRequired) + { + continue; + } + var parameterExample = BuildParameterExample(parameter, useAllParameters); + clientParameterExamples.Add(parameterExample); + } + + return new(client, clientParameterExamples); + } + + internal static InputOperationExample BuildOperationExample(InputOperation operation, bool useAllParameters) + { + _cache.Clear(); + + var parameterExamples = new List(); + foreach (var parameter in operation.Parameters) + { + if (!useAllParameters && !parameter.IsRequired) + { + continue; + } + var parameterExample = BuildParameterExample(parameter, useAllParameters); + parameterExamples.Add(parameterExample); + } + + return new(operation, parameterExamples); + } + + private static InputParameterExample BuildParameterExample(InputParameter parameter, bool useAllParameters) + { + // if the parameter is constant, we just put the constant into the example value instead of mocking a new one + if (parameter.Kind == InputOperationParameterKind.Constant) + { + InputExampleValue value; + if (parameter.DefaultValue != null) + { + // when it is constant, it could have DefaultValue + value = InputExampleValue.Value(parameter.Type, parameter.DefaultValue.Value); + } + else if (parameter.Type is InputUnionType unionType && unionType.UnionItemTypes.First() is InputLiteralType literalType) + { + // or it could be a union of literal types + value = InputExampleValue.Value(parameter.Type, literalType.Value); + } + else + { + // fallback to null + value = InputExampleValue.Null(parameter.Type); + } + return new(parameter, value); + } + + // if the parameter is endpoint + if (parameter.IsEndpoint) + { + var value = InputExampleValue.Value(parameter.Type, EndpointMockValue); + return new(parameter, value); + } + + if (parameter.DefaultValue != null) + { + var value = InputExampleValue.Value(parameter.Type, parameter.DefaultValue.Value); + return new(parameter, value); + } + + var exampleValue = BuildExampleValue(parameter.Type, parameter.Name, useAllParameters, new HashSet()); + return new(parameter, exampleValue); + } + + private static InputExampleValue BuildExampleValue(InputType type, string? hint, bool useAllParameters, HashSet visitedModels) => type switch + { + InputList listType => BuildListExampleValue(listType, hint, useAllParameters, visitedModels), + InputDictionary dictionaryType => BuildDictionaryExampleValue(dictionaryType, hint, useAllParameters, visitedModels), + InputEnumType enumType => BuildEnumExampleValue(enumType), + InputPrimitiveType primitiveType => BuildPrimitiveExampleValue(primitiveType, hint), + InputLiteralType literalType => InputExampleValue.Value(literalType, literalType.Value), + InputModelType modelType => BuildModelExampleValue(modelType, useAllParameters, visitedModels), + InputUnionType unionType => BuildExampleValue(unionType.UnionItemTypes.First(), hint, useAllParameters, visitedModels), + _ => InputExampleValue.Object(type, new Dictionary()) + }; + + private static InputExampleValue BuildListExampleValue(InputList listType, string? hint, bool useAllParameters, HashSet visitedModels) + { + var exampleElementValue = BuildExampleValue(listType.ElementType, hint, useAllParameters, visitedModels); + + return InputExampleValue.List(listType, new[] { exampleElementValue }); + } + + private static InputExampleValue BuildDictionaryExampleValue(InputDictionary dictionaryType, string? hint, bool useAllParameters, HashSet visitedModels) + { + var exampleValue = BuildExampleValue(dictionaryType.ValueType, hint, useAllParameters, visitedModels); + + return InputExampleValue.Object(dictionaryType, new Dictionary + { + ["key"] = exampleValue + }); + } + + private static InputExampleValue BuildEnumExampleValue(InputEnumType enumType) + { + var enumValue = enumType.AllowedValues.First(); + return InputExampleValue.Value(enumType, enumValue.Value); + } + + private static InputExampleValue BuildPrimitiveExampleValue(InputPrimitiveType primitiveType, string? hint) => primitiveType.Kind switch + { + InputTypeKind.Stream => InputExampleValue.Stream(primitiveType, ""), + InputTypeKind.Boolean => InputExampleValue.Value(primitiveType, true), + InputTypeKind.Date => InputExampleValue.Value(primitiveType, "2022-05-10"), + InputTypeKind.DateTime => InputExampleValue.Value(primitiveType, "2022-05-10T14:57:31.2311892-04:00"), + InputTypeKind.DateTimeISO8601 => InputExampleValue.Value(primitiveType, "2022-05-10T18:57:31.2311892Z"), + InputTypeKind.DateTimeRFC1123 => InputExampleValue.Value(primitiveType, "Tue, 10 May 2022 18:57:31 GMT"), + InputTypeKind.DateTimeRFC3339 => InputExampleValue.Value(primitiveType, "2022-05-10T18:57:31.2311892Z"), + InputTypeKind.DateTimeRFC7231 => InputExampleValue.Value(primitiveType, "Tue, 10 May 2022 18:57:31 GMT"), + InputTypeKind.DateTimeUnix => InputExampleValue.Value(primitiveType, 1652209051), + InputTypeKind.Float32 => InputExampleValue.Value(primitiveType, 123.45f), + InputTypeKind.Float64 => InputExampleValue.Value(primitiveType, 123.45d), + InputTypeKind.Float128 => InputExampleValue.Value(primitiveType, 123.45m), + InputTypeKind.Guid => InputExampleValue.Value(primitiveType, "73f411fe-4f43-4b4b-9cbd-6828d8f4cf9a"), + InputTypeKind.Int32 => InputExampleValue.Value(primitiveType, 1234), + InputTypeKind.Int64 => InputExampleValue.Value(primitiveType, 1234L), + InputTypeKind.String => string.IsNullOrWhiteSpace(hint) ? InputExampleValue.Value(primitiveType, "") : InputExampleValue.Value(primitiveType, $"<{hint}>"), + InputTypeKind.DurationISO8601 => InputExampleValue.Value(primitiveType, "PT1H23M45S"), + InputTypeKind.DurationConstant => InputExampleValue.Value(primitiveType, "01:23:45"), + InputTypeKind.Time => InputExampleValue.Value(primitiveType, "01:23:45"), + InputTypeKind.Uri => InputExampleValue.Value(primitiveType, "http://localhost:3000"), + InputTypeKind.DurationSeconds => InputExampleValue.Value(primitiveType, 10), + InputTypeKind.DurationSecondsFloat => InputExampleValue.Value(primitiveType, 10f), + _ => InputExampleValue.Object(primitiveType, new Dictionary()) + }; + + private static InputExampleValue BuildModelExampleValue(InputModelType model, bool useAllParameters, HashSet visitedModels) + { + if (visitedModels.Contains(model)) + return InputExampleValue.Null(model); + + var dict = new Dictionary(); + var result = InputExampleValue.Object(model, dict); + visitedModels.Add(model); + // if this model has a discriminator, we should return a derived type + if (model.DiscriminatorPropertyName != null) + { + var derived = model.DerivedModels.FirstOrDefault(); + if (derived is null) + { + return InputExampleValue.Null(model); + } + else + { + model = derived; + } + } + // then, we just iterate all the properties + foreach (var modelOrBase in model.GetSelfAndBaseModels()) + { + foreach (var property in modelOrBase.Properties) + { + if (property.IsReadOnly) + continue; + + if (!useAllParameters && !property.IsRequired) + continue; + + // this means a property is defined both on the base and derived type, we skip other occurrences only keep the first + // which means we only keep the property defined in the lowest layer (derived types) + if (dict.ContainsKey(property.SerializedName)) + continue; + + InputExampleValue exampleValue; + if (property.IsDiscriminator) + { + exampleValue = InputExampleValue.Value(property.Type, model.DiscriminatorValue!); + } + else + { + exampleValue = BuildExampleValue(property.Type, property.SerializedName, useAllParameters, visitedModels); + } + + dict.Add(property.SerializedName, exampleValue); + } + } + + return result; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputApiKeyAuth.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputApiKeyAuth.cs new file mode 100644 index 0000000000..2cafe9dfae --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputApiKeyAuth.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputApiKeyAuth : InputAuth + { + public InputApiKeyAuth(string name = "", string? prefix = null) : base() + { + Name = name; + Prefix = prefix; + } + + public string Name { get; } + public string? Prefix { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputAuth.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputAuth.cs new file mode 100644 index 0000000000..8173f581cf --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputAuth.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputAuth + { + public InputAuth(InputApiKeyAuth? apiKey, InputOAuth2Auth? oAuth2) + { + ApiKey = apiKey; + OAuth2 = oAuth2; + } + + public InputAuth() : this(null, null) { } + + public InputApiKeyAuth? ApiKey { get; init; } + public InputOAuth2Auth? OAuth2 { get; init; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputClient.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputClient.cs new file mode 100644 index 0000000000..ca188eedc5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputClient.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputClient + { + private readonly string? _key; + private IReadOnlyDictionary? _examples; + + public InputClient(string name, string description, IReadOnlyList operations, bool creatable, IReadOnlyList parameters, string? parent) + { + Name = name; + Description = description; + Operations = operations; + Creatable = creatable; + Parameters = parameters; + Parent = parent; + } + + public InputClient() : this(string.Empty, string.Empty, Array.Empty(), true, Array.Empty(), null) { } + + public string Name { get; } + public string Description { get; } + public IReadOnlyList Operations { get; } + public bool Creatable { get; } + public IReadOnlyList Parameters { get; } + public string? Parent { get; } + + public string Key + { + get => _key ?? Name; + init => _key = value; + } + + public IReadOnlyDictionary Examples => _examples ??= EnsureExamples(); + + private IReadOnlyDictionary EnsureExamples() + { + return new Dictionary() + { + [ExampleMockValueBuilder.ShortVersionMockExampleKey] = ExampleMockValueBuilder.BuildClientExample(this, false), + [ExampleMockValueBuilder.MockExampleAllParameterKey] = ExampleMockValueBuilder.BuildClientExample(this, true) + }; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputClientExample.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputClientExample.cs new file mode 100644 index 0000000000..f8e533daab --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputClientExample.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputClientExample + { + public InputClientExample(InputClient client, IReadOnlyList clientParameters) + { + Client = client; + ClientParameters = clientParameters; + } + + public InputClient Client { get; } + public IReadOnlyList ClientParameters { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputConstant.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputConstant.cs new file mode 100644 index 0000000000..ecb07f2383 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputConstant.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public sealed class InputConstant + { + public InputConstant(object? value, InputType type) + { + Value = value; + Type = type; + } + + public object? Value { get; } + public InputType Type { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputDictionary.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputDictionary.cs new file mode 100644 index 0000000000..c9fed82843 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputDictionary.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + /// + /// Represents an input dictionary type. + /// + public sealed class InputDictionary : InputType + { + /// Creates an instance of . + /// The name of the dictionary. + /// The key's . + /// The value's . + /// Flag used to determine if the input dictionary type is nullable. + public InputDictionary(string name, InputType keyType, InputType valueType, bool isNullable) : base(name, isNullable) + { + KeyType = keyType; + ValueType = valueType; + } + + public InputType KeyType { get; } + public InputType ValueType { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumType.cs new file mode 100644 index 0000000000..373adbe0f5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumType.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputEnumType : InputType + { + public InputEnumType(string name, string? enumNamespace, string? accessibility, string? deprecated, string description, InputModelTypeUsage usage, InputPrimitiveType enumValueType, IReadOnlyList allowedValues, bool isExtensible, bool isNullable) + : base(name, isNullable) + { + Namespace = enumNamespace; + Accessibility = accessibility; + Deprecated = deprecated; + Description = description; + Usage = usage; + EnumValueType = enumValueType; + AllowedValues = allowedValues; + IsExtensible = isExtensible; + } + + public string? Namespace { get; } + public string? Accessibility { get; } + public string? Deprecated { get; } + public string Description { get; } + public InputModelTypeUsage Usage { get; } + public InputPrimitiveType EnumValueType { get; } + public IReadOnlyList AllowedValues { get; } + public bool IsExtensible { get; } + + public static IEqualityComparer IgnoreNullabilityComparer { get; } = new IgnoreNullabilityComparerImplementation(); + + private struct IgnoreNullabilityComparerImplementation : IEqualityComparer + { + public bool Equals(InputEnumType? x, InputEnumType? y) + { + if (x is null || y is null) + { + return ReferenceEquals(x, y); + } + + if (x.GetType() != y.GetType()) + { + return false; + } + + return x.Name == y.Name + && x.Namespace == y.Namespace + && x.Accessibility == y.Accessibility + && x.Description == y.Description + && x.EnumValueType.Equals(y.EnumValueType) + && x.AllowedValues.SequenceEqual(y.AllowedValues) + && x.IsExtensible == y.IsExtensible; + } + + public int GetHashCode(InputEnumType obj) + { + var hashCode = new HashCode(); + hashCode.Add(obj.Name); + hashCode.Add(obj.Namespace); + hashCode.Add(obj.Accessibility); + hashCode.Add(obj.Description); + hashCode.Add(obj.EnumValueType); + hashCode.Add(obj.IsExtensible); + foreach (var item in obj.AllowedValues) + { + hashCode.Add(item); + } + + return hashCode.ToHashCode(); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeFloatValue.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeFloatValue.cs new file mode 100644 index 0000000000..87474a3581 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeFloatValue.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + internal class InputEnumTypeFloatValue : InputEnumTypeValue + { + public InputEnumTypeFloatValue(string name, float floatValue, string? description) : base(name, floatValue, description) + { + FloatValue = floatValue; + } + + public float FloatValue { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeIntegerValue.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeIntegerValue.cs new file mode 100644 index 0000000000..d4e6acfbfd --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeIntegerValue.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + internal class InputEnumTypeIntegerValue : InputEnumTypeValue + { + public InputEnumTypeIntegerValue(string name, int integerValue, string? description) : base(name, integerValue, description) + { + IntegerValue = integerValue; + } + + public int IntegerValue { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeStringValue.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeStringValue.cs new file mode 100644 index 0000000000..2b81194042 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeStringValue.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + internal class InputEnumTypeStringValue : InputEnumTypeValue + { + public InputEnumTypeStringValue(string name, string stringValue, string? description) : base(name, stringValue, description) + { + StringValue = stringValue; + } + + public string StringValue { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeValue.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeValue.cs new file mode 100644 index 0000000000..9a89d36733 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputEnumTypeValue.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputEnumTypeValue + { + public InputEnumTypeValue(string name, object value, string? description) + { + Name = name; + Value = value; + Description = description; + } + + public string Name { get; } + public object Value { get; } + public string? Description { get; } + + public virtual string GetJsonValueString() => GetValueString(); + public string GetValueString() => (Value.ToString() ?? string.Empty); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleListValue.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleListValue.cs new file mode 100644 index 0000000000..b0be8db6ef --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleListValue.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Input +{ + internal class InputExampleListValue : InputExampleValue + { + public InputExampleListValue(InputType type, IReadOnlyList values) : base(type) + { + Values = values; + } + + public IReadOnlyList Values { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleObjectValue.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleObjectValue.cs new file mode 100644 index 0000000000..dc8bcbb3f9 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleObjectValue.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Input +{ + internal class InputExampleObjectValue : InputExampleValue + { + public InputExampleObjectValue(InputType type, IReadOnlyDictionary values) : base(type) + { + Values = values; + } + + public IReadOnlyDictionary Values { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleRawValue.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleRawValue.cs new file mode 100644 index 0000000000..53bfe88ed6 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleRawValue.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + internal class InputExampleRawValue : InputExampleValue + { + public InputExampleRawValue(InputType type, object? rawValue) : base(type) + { + RawValue = rawValue; + } + + public object? RawValue { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleStreamValue.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleStreamValue.cs new file mode 100644 index 0000000000..68c525d1b7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleStreamValue.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + internal class InputExampleStreamValue : InputExampleValue + { + public InputExampleStreamValue(InputType type, string filename) : base(type) + { + Filename = filename; + } + + public string Filename { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleValue.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleValue.cs new file mode 100644 index 0000000000..079debf493 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputExampleValue.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Input +{ + public abstract class InputExampleValue + { + protected InputExampleValue(InputType type) + { + Type = type; + } + + public InputType Type { get; } + + public static InputExampleValue Null(InputType type) => new InputExampleRawValue(type, null); + public static InputExampleValue Value(InputType type, object? rawValue) => new InputExampleRawValue(type, rawValue); + public static InputExampleValue List(InputType type, IReadOnlyList values) => new InputExampleListValue(type, values); + public static InputExampleValue Object(InputType type, IReadOnlyDictionary properties) => new InputExampleObjectValue(type, properties); + public static InputExampleValue Stream(InputType type, string filename) => new InputExampleStreamValue(type, filename); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputGenericType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputGenericType.cs new file mode 100644 index 0000000000..a842c891d3 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputGenericType.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputGenericType : InputType + { + public InputGenericType(Type type, InputType argumentType, bool isNullable) : base(type.Name, isNullable) + { + Type = type; + ArgumentType = argumentType; + } + + public Type Type { get; } + public InputType ArgumentType { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputIntrinsicType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputIntrinsicType.cs new file mode 100644 index 0000000000..e56f40cf7f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputIntrinsicType.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputIntrinsicType : InputType + { + public InputIntrinsicType(InputIntrinsicTypeKind kind) : base(kind.ToString(), false) + { + Kind = kind; + } + + public InputIntrinsicTypeKind Kind { get; } + + public static InputIntrinsicType ErrorType { get; } = new(InputIntrinsicTypeKind.ErrorType); + public static InputIntrinsicType Void { get; } = new(InputIntrinsicTypeKind.Void); + public static InputIntrinsicType Never { get; } = new(InputIntrinsicTypeKind.Never); + public static InputIntrinsicType Unknown { get; } = new(InputIntrinsicTypeKind.Unknown); + public static InputIntrinsicType Null { get; } = new(InputIntrinsicTypeKind.Null); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputIntrinsicTypeKind.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputIntrinsicTypeKind.cs new file mode 100644 index 0000000000..c1d98450e3 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputIntrinsicTypeKind.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public enum InputIntrinsicTypeKind + { + ErrorType, + Void, + Never, + Unknown, + Null, + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputList.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputList.cs new file mode 100644 index 0000000000..ad10b96043 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputList.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + /// + /// Represents an input list type. + /// + public sealed class InputList : InputType + { + /// Creates an instance of . + /// The name of the list type. + /// The element's . + /// Flag used to determine if the input list type is embedding vector. + /// Flag used to determine if the input list type is nullable. + public InputList(string name, InputType elementType, bool isEmbeddingsVector, bool isNullable) : base(name, isNullable) + { + ElementType = elementType; + IsEmbeddingsVector = isEmbeddingsVector; + } + + public InputType ElementType { get; } + public bool IsEmbeddingsVector { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputLiteralType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputLiteralType.cs new file mode 100644 index 0000000000..89b895dd55 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputLiteralType.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public sealed class InputLiteralType : InputType + { + public InputLiteralType(string name, InputType literalValueType, object value, bool isNullable) : base(name, isNullable) + { + LiteralValueType = literalValueType; + Value = value; + } + + public InputType LiteralValueType { get; } + public object Value { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputModelProperty.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputModelProperty.cs new file mode 100644 index 0000000000..d7b1386a2f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputModelProperty.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputModelProperty + { + public InputModelProperty(string name, string serializedName, string description, InputType type, bool isRequired, bool isReadOnly, bool isDiscriminator) + { + Name = name; + SerializedName = serializedName; + Description = description; + Type = type; + IsRequired = isRequired; + IsReadOnly = isReadOnly; + IsDiscriminator = isDiscriminator; + } + + public string Name { get; } + public string SerializedName { get; } + public string Description { get; } + public InputType Type { get; } + public bool IsRequired { get; } + public bool IsReadOnly { get; } + public bool IsDiscriminator { get; } + public FormattableString? DefaultValue { get; init; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputModelType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputModelType.cs new file mode 100644 index 0000000000..fad395e15d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputModelType.cs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputModelType : InputType + { + public InputModelType(string name, string? modelNamespace, string? accessibility, string? deprecated, string? description, InputModelTypeUsage usage, IReadOnlyList properties, InputModelType? baseModel, IReadOnlyList derivedModels, string? discriminatorValue, string? discriminatorPropertyName, InputDictionary? inheritedDictionaryType, bool isNullable) + : base(name, isNullable) + { + Namespace = modelNamespace; + Accessibility = accessibility; + Deprecated = deprecated; + Description = description; + Usage = usage; + Properties = properties; + BaseModel = baseModel; + DerivedModels = derivedModels; + DiscriminatorValue = discriminatorValue; + DiscriminatorPropertyName = discriminatorPropertyName; + InheritedDictionaryType = inheritedDictionaryType; + IsUnknownDiscriminatorModel = false; + IsPropertyBag = false; + } + + public string? Namespace { get; } + public string? Accessibility { get; } + public string? Deprecated { get; } + public string? Description { get; } + public InputModelTypeUsage Usage { get; } + public IReadOnlyList Properties { get; } + public InputModelType? BaseModel { get; private set; } + public IReadOnlyList DerivedModels { get; } + public string? DiscriminatorValue { get; } + public string? DiscriminatorPropertyName { get; } + public InputDictionary? InheritedDictionaryType { get; } + public bool IsUnknownDiscriminatorModel { get; init; } + public bool IsPropertyBag { get; init; } + + internal void SetBaseModel(InputModelType? baseModel, [CallerFilePath] string filepath = "", [CallerMemberName] string caller = "") + { + Debug.Assert(filepath.EndsWith($"{nameof(TypeSpecInputModelTypeConverter)}.cs"), $"This method is only allowed to be called in `TypeSpecInputModelTypeConverter.cs`"); + Debug.Assert(caller == nameof(TypeSpecInputModelTypeConverter.CreateModelType), $"This method is only allowed to be called in `TypeSpecInputModelTypeConverter.CreateModelType`"); + BaseModel = baseModel; + } + + public IEnumerable GetSelfAndBaseModels() => EnumerateBase(this); + + public IEnumerable GetAllBaseModels() => EnumerateBase(BaseModel); + + public IReadOnlyList GetAllDerivedModels() + { + var list = new List(DerivedModels); + for (var i = 0; i < list.Count; i++) + { + list.AddRange(list[i].DerivedModels); + } + + return list; + } + + private static IEnumerable EnumerateBase(InputModelType? model) + { + while (model != null) + { + yield return model; + model = model.BaseModel; + } + } + + internal InputModelType ReplaceProperty(InputModelProperty property, InputType inputType) + { + return new InputModelType( + Name, + Namespace, + Accessibility, + Deprecated, + Description, + Usage, + GetNewProperties(property, inputType), + BaseModel, + DerivedModels, + DiscriminatorValue, + DiscriminatorPropertyName, + InheritedDictionaryType, + IsNullable); + } + + private IReadOnlyList GetNewProperties(InputModelProperty property, InputType inputType) + { + List properties = new List(); + foreach (var myProperty in Properties) + { + if (myProperty.Equals(property)) + { + properties.Add(new InputModelProperty( + myProperty.Name, + myProperty.SerializedName, + myProperty.Description, + myProperty.Type.GetCollectionEquivalent(inputType), + myProperty.IsRequired, + myProperty.IsReadOnly, + myProperty.IsDiscriminator)); + } + else + { + properties.Add(myProperty); + } + } + return properties; + } + + public bool Equals(InputType other, bool handleCollections) + { + if (!handleCollections) + return Equals(other); + + switch (other) + { + case InputDictionary otherDictionary: + return Equals(otherDictionary.ValueType); + case InputList otherList: + return Equals(otherList.ElementType); + default: + return Equals(other); + } + } + + internal InputModelProperty? GetProperty(InputModelType key) + { + foreach (var property in Properties) + { + if (key.Equals(property.Type, true)) + return property; + } + return null; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputModelTypeUsage.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputModelTypeUsage.cs new file mode 100644 index 0000000000..e6724cf344 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputModelTypeUsage.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Input +{ + [Flags] + public enum InputModelTypeUsage + { + None = 0, + Input = 1, + Output = 2, + RoundTrip = Input | Output + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputNamespace.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputNamespace.cs new file mode 100644 index 0000000000..4d32b0c56a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputNamespace.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputNamespace + { + public InputNamespace(string name, string description, IReadOnlyList apiVersions, IReadOnlyList enums, IReadOnlyList models, IReadOnlyList clients, InputAuth auth) + { + Name = name; + Description = description; + ApiVersions = apiVersions; + Enums = enums; + Models = models; + Clients = clients; + Auth = auth; + } + + public InputNamespace() : this(name: string.Empty, description: string.Empty, apiVersions: Array.Empty(), enums: Array.Empty(), models: Array.Empty(), clients: Array.Empty(), auth: new InputAuth()) { } + + public string Name { get; init; } + public string Description { get; init; } + public IReadOnlyList ApiVersions { get; init; } + public IReadOnlyList Enums { get; init; } + public IReadOnlyList Models { get; init; } + public IReadOnlyList Clients { get; init; } + public InputAuth Auth { get; init; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOAuth2Auth.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOAuth2Auth.cs new file mode 100644 index 0000000000..f49b21cd69 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOAuth2Auth.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputOAuth2Auth : InputAuth + { + public InputOAuth2Auth(IReadOnlyCollection scopes) : base() + { + Scopes = scopes; + } + + public InputOAuth2Auth() : this(Array.Empty()) { } + + public IReadOnlyCollection Scopes { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOperation.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOperation.cs new file mode 100644 index 0000000000..f05ee67406 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOperation.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Input +{ + /// + /// Represents an input operation. + /// + public class InputOperation + { + public InputOperation( + string name, + string? resourceName, + string? summary, + string? deprecated, + string description, + string? accessibility, + IReadOnlyList parameters, + IReadOnlyList responses, + string httpMethod, + BodyMediaType requestBodyMediaType, + string uri, + string path, + string? externalDocsUrl, + IReadOnlyList? requestMediaTypes, + bool bufferResponse, + OperationLongRunning? longRunning, + OperationPaging? paging, + bool generateProtocolMethod, + bool generateConvenienceMethod) + { + Name = name; + ResourceName = resourceName; + Summary = summary; + Deprecated = deprecated; + Description = description; + Accessibility = accessibility; + Parameters = parameters; + Responses = responses; + HttpMethod = httpMethod; + RequestBodyMediaType = requestBodyMediaType; + Uri = uri; + Path = path; + ExternalDocsUrl = externalDocsUrl; + RequestMediaTypes = requestMediaTypes; + BufferResponse = bufferResponse; + LongRunning = longRunning; + Paging = paging; + GenerateProtocolMethod = generateProtocolMethod; + GenerateConvenienceMethod = generateConvenienceMethod; + } + + public InputOperation() : this( + name: string.Empty, + resourceName: null, + summary: null, + deprecated: null, + description: string.Empty, + accessibility: null, + parameters: Array.Empty(), + responses: Array.Empty(), + httpMethod: string.Empty, + requestBodyMediaType: BodyMediaType.None, + uri: string.Empty, + path: string.Empty, + externalDocsUrl: null, + requestMediaTypes: Array.Empty(), + bufferResponse: false, + longRunning: null, + paging: null, + generateProtocolMethod: true, + generateConvenienceMethod: false) + { } + + public string Name { get; } + public string? ResourceName { get; } + public string? Summary { get; } + public string? Deprecated { get; } + public string Description { get; } + public string? Accessibility { get; } + public IReadOnlyList Parameters { get; } + public IReadOnlyList Responses { get; } + public string HttpMethod { get; } + public BodyMediaType RequestBodyMediaType { get; } + public string Uri { get; } + public string Path { get; } + public string? ExternalDocsUrl { get; } + public IReadOnlyList? RequestMediaTypes { get; } + public bool BufferResponse { get; } + public OperationLongRunning? LongRunning { get; } + public OperationPaging? Paging { get; } + public bool GenerateProtocolMethod { get; } + public bool GenerateConvenienceMethod { get; } + + private IReadOnlyDictionary? _examples; + internal IReadOnlyDictionary Examples => _examples ??= EnsureExamples(); + + private IReadOnlyDictionary EnsureExamples() + { + return new Dictionary() + { + [ExampleMockValueBuilder.ShortVersionMockExampleKey] = ExampleMockValueBuilder.BuildOperationExample(this, false), + [ExampleMockValueBuilder.MockExampleAllParameterKey] = ExampleMockValueBuilder.BuildOperationExample(this, true) + }; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOperationExample.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOperationExample.cs new file mode 100644 index 0000000000..cc0b17c81c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOperationExample.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Input +{ + internal class InputOperationExample + { + public InputOperationExample(InputOperation operation, IReadOnlyList parameters) + { + Operation = operation; + Parameters = parameters; + } + + public InputOperation Operation { get; } + public IReadOnlyList Parameters { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOperationParameterKind.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOperationParameterKind.cs new file mode 100644 index 0000000000..1dc310eda4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputOperationParameterKind.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public enum InputOperationParameterKind + { + Method = 0, + Client = 1, + Constant = 2, + Flattened = 3, + Spread = 4, + Grouped = 5, + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputParameter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputParameter.cs new file mode 100644 index 0000000000..fb891ee772 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputParameter.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public sealed class InputParameter + { + public InputParameter( + string name, + string nameInRequest, + string? description, + InputType type, + RequestLocation location, + InputConstant? defaultValue, + InputParameter? groupedBy, + InputOperationParameterKind kind, + bool isRequired, + bool isApiVersion, + bool isResourceParameter, + bool isContentType, + bool isEndpoint, + bool skipUrlEncoding, + bool explode, + string? arraySerializationDelimiter, + string? headerCollectionPrefix) + { + Name = name; + NameInRequest = nameInRequest; + Description = description; + Type = type; + Location = location; + DefaultValue = defaultValue; + GroupedBy = groupedBy; + Kind = kind; + IsRequired = isRequired; + IsApiVersion = isApiVersion; + IsResourceParameter = isResourceParameter; + IsContentType = isContentType; + IsEndpoint = isEndpoint; + SkipUrlEncoding = skipUrlEncoding; + Explode = explode; + ArraySerializationDelimiter = arraySerializationDelimiter; + HeaderCollectionPrefix = headerCollectionPrefix; + } + + public InputParameter() : this( + name: string.Empty, + nameInRequest: string.Empty, + description: null, + type: InputPrimitiveType.Object, + location: RequestLocation.None, + defaultValue: null, + groupedBy: null, + kind: InputOperationParameterKind.Method, + isRequired: false, + isApiVersion: false, + isResourceParameter: false, + isContentType: false, + isEndpoint: false, + skipUrlEncoding: false, + explode: false, + arraySerializationDelimiter: null, + headerCollectionPrefix: null) + { } + + public string Name { get; } + public string NameInRequest { get; } + public string? Description { get; } + public InputType Type { get; } + public RequestLocation Location { get; } + public InputConstant? DefaultValue { get; } + public InputParameter? GroupedBy { get; } + public InputOperationParameterKind Kind { get; } + public bool IsRequired { get; } + public bool IsApiVersion { get; } + public bool IsResourceParameter { get; } + public bool IsContentType { get; } + public bool IsEndpoint { get; } + public bool SkipUrlEncoding { get; } + public bool Explode { get; } + public string? ArraySerializationDelimiter { get; } + public string? HeaderCollectionPrefix { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputParameterExample.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputParameterExample.cs new file mode 100644 index 0000000000..d8dc38b1c3 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputParameterExample.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputParameterExample + { + public InputParameterExample(InputParameter parameter, InputExampleValue exampleValue) + { + Parameter = parameter; + ExampleValue = exampleValue; + } + + public InputParameter Parameter { get; } + public InputExampleValue ExampleValue { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputPrimitiveType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputPrimitiveType.cs new file mode 100644 index 0000000000..d4b875f849 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputPrimitiveType.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public sealed class InputPrimitiveType : InputType + { + public InputPrimitiveType(InputTypeKind kind, bool isNullable = false) : base(kind.ToString(), isNullable) + { + Kind = kind; + } + + public InputTypeKind Kind { get; } + + public static InputPrimitiveType BinaryData { get; } = new(InputTypeKind.BinaryData); + public static InputPrimitiveType Boolean { get; } = new(InputTypeKind.Boolean); + public static InputPrimitiveType Bytes { get; } = new(InputTypeKind.Bytes); + public static InputPrimitiveType BytesBase64Url { get; } = new(InputTypeKind.BytesBase64Url); + public static InputPrimitiveType ContentType { get; } = new(InputTypeKind.ContentType); + public static InputPrimitiveType Date { get; } = new(InputTypeKind.Date); + public static InputPrimitiveType DateTime { get; } = new(InputTypeKind.DateTime); + public static InputPrimitiveType DateTimeISO8601 { get; } = new(InputTypeKind.DateTimeISO8601); + public static InputPrimitiveType DateTimeRFC1123 { get; } = new(InputTypeKind.DateTimeRFC1123); + public static InputPrimitiveType DateTimeRFC3339 { get; } = new(InputTypeKind.DateTimeRFC3339); + public static InputPrimitiveType DateTimeRFC7231 { get; } = new(InputTypeKind.DateTimeRFC7231); + public static InputPrimitiveType DateTimeUnix { get; } = new(InputTypeKind.DateTimeUnix); + public static InputPrimitiveType DurationISO8601 { get; } = new(InputTypeKind.DurationISO8601); + public static InputPrimitiveType DurationConstant { get; } = new(InputTypeKind.DurationConstant); + public static InputPrimitiveType ETag { get; } = new(InputTypeKind.ETag); + public static InputPrimitiveType Float32 { get; } = new(InputTypeKind.Float32); + public static InputPrimitiveType Float64 { get; } = new(InputTypeKind.Float64); + public static InputPrimitiveType Float128 { get; } = new(InputTypeKind.Float128); + public static InputPrimitiveType Guid { get; } = new(InputTypeKind.Guid); + public static InputPrimitiveType Int32 { get; } = new(InputTypeKind.Int32); + public static InputPrimitiveType Int64 { get; } = new(InputTypeKind.Int64); + public static InputPrimitiveType IPAddress { get; } = new(InputTypeKind.IPAddress); + public static InputPrimitiveType Object { get; } = new(InputTypeKind.Object); + public static InputPrimitiveType RequestMethod { get; } = new(InputTypeKind.RequestMethod); + public static InputPrimitiveType ResourceIdentifier { get; } = new(InputTypeKind.ResourceIdentifier); + public static InputPrimitiveType ResourceType { get; } = new(InputTypeKind.ResourceType); + public static InputPrimitiveType Stream { get; } = new(InputTypeKind.Stream); + public static InputPrimitiveType String { get; } = new(InputTypeKind.String); + public static InputPrimitiveType Time { get; } = new(InputTypeKind.Time); + public static InputPrimitiveType Uri { get; } = new(InputTypeKind.Uri); + + public bool IsNumber => Kind is InputTypeKind.Int32 or InputTypeKind.Int64 or InputTypeKind.Float32 or InputTypeKind.Float64 or InputTypeKind.Float128; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputType.cs new file mode 100644 index 0000000000..c5545e9b47 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputType.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + /// + /// Represents an input type to the generator. + /// + /// The name of the input type. + /// Flag to determine if the type is nullable. + public abstract class InputType + { + protected InputType(string name, bool isNullable) + { + Name = name; + IsNullable = isNullable; + } + + public string Name { get; } + public bool IsNullable { get; } + + internal InputType GetCollectionEquivalent(InputType inputType) + { + switch (this) + { + case InputList listType: + return new InputList( + listType.Name, + listType.ElementType.GetCollectionEquivalent(inputType), + listType.IsEmbeddingsVector, + listType.IsNullable); + case InputDictionary dictionaryType: + return new InputDictionary( + dictionaryType.Name, + dictionaryType.KeyType, + dictionaryType.ValueType.GetCollectionEquivalent(inputType), + dictionaryType.IsNullable); + default: + return inputType; + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputTypeKind.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputTypeKind.cs new file mode 100644 index 0000000000..6c25fdcda9 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputTypeKind.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + public enum InputTypeKind + { + Boolean, + BinaryData, + Bytes, + BytesBase64Url, + ContentType, + Date, + DateTime, + DateTimeISO8601, + DateTimeRFC1123, + DateTimeRFC3339, + DateTimeRFC7231, + DateTimeUnix, + Decimal, + Decimal128, + DurationISO8601, + DurationConstant, + DurationSeconds, + DurationSecondsFloat, + ETag, + Float32, + Float64, + Float128, + Guid, + Int32, + Int64, + SafeInt, + IPAddress, + Object, + RequestMethod, + ResourceIdentifier, + ResourceType, + Stream, + String, + Time, + Uri, + SByte, + Byte + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputUnionType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputUnionType.cs new file mode 100644 index 0000000000..1968b11999 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/InputUnionType.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Input +{ + public class InputUnionType : InputType + { + public InputUnionType(string name, IReadOnlyList unionItemTypes, bool isNullable) : base(name, isNullable) + { + UnionItemTypes = unionItemTypes; + } + + public IReadOnlyList UnionItemTypes { get; } + + internal IReadOnlyList GetEnum() + { + if (!IsAllLiteralStringPlusString()) + throw new InvalidOperationException($"Cannot convert union '{this}' to enum because its not all strings"); + + var values = new List(); + foreach (var item in UnionItemTypes) + { + if (item is not InputLiteralType literalType) + continue; + values.Add(new InputEnumTypeValue(FirstCharToUpperCase(literalType.Value.ToString()!), literalType.Value, literalType.Value.ToString())); + } + return values; + } + + internal bool IsAllLiteralString() + { + foreach (var item in UnionItemTypes) + { + if (item is not InputLiteralType literal) + return false; + + if (literal.LiteralValueType is not InputPrimitiveType primitive) + return false; + + if (primitive.Kind != InputTypeKind.String) + return false; + } + + return true; + } + + internal bool IsAllLiteralStringPlusString() + { + foreach (var item in UnionItemTypes) + { + InputPrimitiveType? primitive = item is InputLiteralType literal ? literal.LiteralValueType as InputPrimitiveType : item as InputPrimitiveType; + if (primitive is null) + return false; + if (primitive.Kind != InputTypeKind.String) + return false; + } + + return true; + } + + private static string FirstCharToUpperCase(string str) + { + if (string.IsNullOrEmpty(str) || char.IsUpper(str[0])) + return str; + + return char.ToUpper(str[0]) + str.Substring(1); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationLongRunning.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationLongRunning.cs new file mode 100644 index 0000000000..c43497dc62 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationLongRunning.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Linq; + +namespace Microsoft.Generator.CSharp.Input +{ + public sealed class OperationLongRunning + { + public OperationLongRunning(int finalStateVia, OperationResponse finalResponse, string? resultPath) + { + FinalStateVia = finalStateVia; + FinalResponse = finalResponse; + ResultPath = resultPath; + } + + public OperationLongRunning() : this(1, new OperationResponse(), null) { } + + public int FinalStateVia { get; } + public OperationResponse FinalResponse { get; } + public string? ResultPath { get; } + + /// + /// Meaningful return type of the long running operation. + /// + public InputType? ReturnType + { + get + { + if (FinalResponse.BodyType is null) + return null; + + if (ResultPath is null) + return FinalResponse.BodyType; + + var rawResponseType = (InputModelType)FinalResponse.BodyType; + return rawResponseType.Properties.FirstOrDefault(p => p.SerializedName == ResultPath)!.Type; + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationPaging.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationPaging.cs new file mode 100644 index 0000000000..4d47549f36 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationPaging.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Input +{ + public sealed class OperationPaging + { + public OperationPaging(string? nextLinkName = null, string? itemName = null) + { + NextLinkName = nextLinkName; + ItemName = itemName; + } + + public string? NextLinkName { get; } + public string? ItemName { get; } + internal Func? NextLinkOperationRef { get; init; } + internal InputOperation? NextLinkOperation => NextLinkOperationRef?.Invoke() ?? null; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationResponse.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationResponse.cs new file mode 100644 index 0000000000..091638cc8b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationResponse.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Input +{ + /// + /// Represents an operation response. + /// + public sealed class OperationResponse + { + public OperationResponse(IReadOnlyList statusCodes, InputType? bodyType, BodyMediaType bodyMediaType, IReadOnlyList headers, bool isErrorResponse, IReadOnlyList contentTypes) + { + StatusCodes = statusCodes; + BodyType = bodyType; + BodyMediaType = bodyMediaType; + Headers = headers; + IsErrorResponse = isErrorResponse; + ContentTypes = contentTypes; + } + + public OperationResponse() : this(Array.Empty(), null, BodyMediaType.None, Array.Empty(), false, Array.Empty()) { } + + public IReadOnlyList StatusCodes { get; } + public InputType? BodyType { get; } + public BodyMediaType BodyMediaType { get; } + public IReadOnlyList Headers { get; } + public bool IsErrorResponse { get; } + public IReadOnlyList ContentTypes { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationResponseHeader.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationResponseHeader.cs new file mode 100644 index 0000000000..3851f632ae --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/OperationResponseHeader.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + /// + /// Represents an operation response header. + /// + public sealed class OperationResponseHeader + { + /// Creates an instance of . + /// The name of the header. + /// The name of the header in the operation response. + /// The description of the header. + /// The input type. + public OperationResponseHeader(string name, string nameInResponse, string description, InputType type) + { + Name = name; + NameInResponse = nameInResponse; + Description = description; + Type = type; + } + + public OperationResponseHeader() : this(string.Empty, string.Empty, string.Empty, InputPrimitiveType.String) { } + + public string Name { get; } + public string NameInResponse { get; } + public string Description { get; } + public InputType Type { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/RequestLocation.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/RequestLocation.cs new file mode 100644 index 0000000000..74ab8cccf4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/RequestLocation.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Input +{ + /// + /// Represents the HTTP request location header. + /// + public enum RequestLocation + { + None, + Uri, + Path, + Query, + Header, + Body, + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputConstantConverter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputConstantConverter.cs new file mode 100644 index 0000000000..c07305bb24 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputConstantConverter.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal class TypeSpecInputConstantConverter : JsonConverter + { + private readonly TypeSpecReferenceHandler _referenceHandler; + + public TypeSpecInputConstantConverter(TypeSpecReferenceHandler referenceHandler) + { + _referenceHandler = referenceHandler; + } + + public override InputConstant Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => reader.ReadReferenceAndResolve(_referenceHandler.CurrentResolver) ?? CreateInputConstant(ref reader, null, options, _referenceHandler.CurrentResolver); + + public override void Write(Utf8JsonWriter writer, InputConstant value, JsonSerializerOptions options) + => throw new NotSupportedException("Writing not supported"); + + public static InputConstant CreateInputConstant(ref Utf8JsonReader reader, string? id, JsonSerializerOptions options, ReferenceResolver resolver) + { + var isFirstProperty = id == null; + InputType? type = null; + + reader.TryReadReferenceId(ref isFirstProperty, ref id); + if (!reader.TryReadWithConverter(nameof(InputConstant.Type), options, ref type)) + { + throw new JsonException("Must provide type ahead of value."); + } + var value = ReadConstantValue(ref reader, nameof(InputConstant.Value), type); + + type = type ?? throw new JsonException("InputConstant must have type"); + + value = value ?? throw new JsonException("InputConstant must have value"); + + var constant = new InputConstant(value, type); + if (id != null) + { + resolver.AddReference(id, constant); + } + return constant; + } + + public static object ReadConstantValue(ref Utf8JsonReader reader, string propertyName, InputType? type) + { + if (type == null) + { + throw new JsonException("Must place type ahead of value."); + } + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + if (reader.GetString() != propertyName) + { + throw new JsonException("This is not for json field " + propertyName); + } + + reader.Read(); + object? value; + switch (type) { + case InputPrimitiveType primitiveType: + switch (primitiveType.Kind) + { + case InputTypeKind.String: + value = reader.GetString() ?? throw new JsonException(); + break; + case InputTypeKind.Uri: + var stringvalue = reader.GetString() ?? throw new JsonException(); + value = new Uri(stringvalue); + break; + case InputTypeKind.Int32: + value = reader.GetInt32(); + break; + case InputTypeKind.Int64: + value = reader.GetInt64(); + break; + case InputTypeKind.Boolean: + value = reader.GetBoolean(); + break; + default: + value = reader.GetString() ?? throw new JsonException(); + break; + + } + break; + case InputEnumType enumType: + switch (enumType.EnumValueType.Kind) + { + case InputTypeKind.String: + value = reader.GetString() ?? throw new JsonException(); + break; + case InputTypeKind.Int32: + value = reader.GetInt32(); + break; + case InputTypeKind.Float32: + value = reader.GetDouble(); + break; + default: + throw new JsonException($"Unsupported enum value type: {enumType.EnumValueType.Kind}"); + } + break; + case InputLiteralType literalType: + value = literalType.Value; + break; + default: + throw new JsonException($"Not supported type: {type.GetType()}"); + } + reader.Read(); + return value; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputDictionaryTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputDictionaryTypeConverter.cs new file mode 100644 index 0000000000..18a15a6c34 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputDictionaryTypeConverter.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal sealed class TypeSpecInputDictionaryTypeConverter : JsonConverter + { + private readonly TypeSpecReferenceHandler _referenceHandler; + + public TypeSpecInputDictionaryTypeConverter(TypeSpecReferenceHandler referenceHandler) + { + _referenceHandler = referenceHandler; + } + + public override InputDictionary? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => reader.ReadReferenceAndResolve(_referenceHandler.CurrentResolver) ?? CreateDictionaryType(ref reader, null, null, options, _referenceHandler.CurrentResolver); + + public override void Write(Utf8JsonWriter writer, InputDictionary value, JsonSerializerOptions options) + => throw new NotSupportedException("Writing not supported"); + + public static InputDictionary CreateDictionaryType(ref Utf8JsonReader reader, string? id, string? name, JsonSerializerOptions options, ReferenceResolver resolver) + { + var isFirstProperty = id == null && name == null; + bool isNullable = false; + InputType? keyType = null; + InputType? valueType = null; + while (reader.TokenType != JsonTokenType.EndObject) + { + var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadString(nameof(InputDictionary.Name), ref name) + || reader.TryReadBoolean(nameof(InputDictionary.IsNullable), ref isNullable) + || reader.TryReadWithConverter(nameof(InputDictionary.KeyType), options, ref keyType) + || reader.TryReadWithConverter(nameof(InputDictionary.ValueType), options, ref valueType); + + if (!isKnownProperty) + { + reader.SkipProperty(); + } + } + + name = name ?? throw new JsonException("Dictionary must have name"); + keyType = keyType ?? throw new JsonException("Dictionary must have key type"); + valueType = valueType ?? throw new JsonException("Dictionary must have value type"); + + var dictType = new InputDictionary(name, keyType, valueType, isNullable); + if (id != null) + { + resolver.AddReference(id, dictType); + } + return dictType; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputEnumTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputEnumTypeConverter.cs new file mode 100644 index 0000000000..9d4f6ee8b8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputEnumTypeConverter.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal sealed class TypeSpecInputEnumTypeConverter : JsonConverter + { + private readonly TypeSpecReferenceHandler _referenceHandler; + + public TypeSpecInputEnumTypeConverter(TypeSpecReferenceHandler referenceHandler) + { + _referenceHandler = referenceHandler; + } + + public override InputEnumType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => reader.ReadReferenceAndResolve(_referenceHandler.CurrentResolver) ?? CreateEnumType(ref reader, null, null, options, _referenceHandler.CurrentResolver); + + public override void Write(Utf8JsonWriter writer, InputEnumType value, JsonSerializerOptions options) + => throw new NotSupportedException("Writing not supported"); + + public static InputEnumType CreateEnumType(ref Utf8JsonReader reader, string? id, string? name, JsonSerializerOptions options, ReferenceResolver resolver) + { + var isFirstProperty = id == null && name == null; + bool isNullable = false; + string? ns = null; + string? accessibility = null; + string? deprecated = null; + string? description = null; + InputModelTypeUsage usage = InputModelTypeUsage.None; + string? usageString = null; + bool isExtendable = false; + InputPrimitiveType? valueType = null; + IReadOnlyList? allowedValues = null; + while (reader.TokenType != JsonTokenType.EndObject) + { + var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadString(nameof(InputEnumType.Name), ref name) + || reader.TryReadBoolean(nameof(InputEnumType.IsNullable), ref isNullable) + || reader.TryReadString(nameof(InputEnumType.Namespace), ref ns) + || reader.TryReadString(nameof(InputEnumType.Accessibility), ref accessibility) + || reader.TryReadString(nameof(InputEnumType.Deprecated), ref deprecated) + || reader.TryReadString(nameof(InputEnumType.Description), ref description) + || reader.TryReadString(nameof(InputEnumType.Usage), ref usageString) + || reader.TryReadBoolean(nameof(InputEnumType.IsExtensible), ref isExtendable) + || reader.TryReadPrimitiveType(nameof(InputEnumType.EnumValueType), ref valueType) + || reader.TryReadWithConverter(nameof(InputEnumType.AllowedValues), options, ref allowedValues); + + if (!isKnownProperty) + { + reader.SkipProperty(); + } + } + + name = name ?? throw new JsonException("Enum must have name"); + if (description == null) + { + description = ""; + Console.Error.WriteLine($"[Warn]: Enum '{name}' must have a description"); + } + + if (usageString != null) + { + Enum.TryParse(usageString, ignoreCase: true, out usage); + } + + if (allowedValues == null || allowedValues.Count == 0) + { + throw new JsonException("Enum must have at least one value"); + } + + InputPrimitiveType? currentType = null; + foreach (var value in allowedValues) + { + switch (value.Value) + { + case int i: + if (currentType == InputPrimitiveType.String) + throw new JsonException($"Enum value types are not consistent."); + if (currentType != InputPrimitiveType.Float32) + currentType = InputPrimitiveType.Int32; + break; + case float f: + if (currentType == InputPrimitiveType.String) + throw new JsonException($"Enum value types are not consistent."); + currentType = InputPrimitiveType.Float32; + break; + case string: + if (currentType == InputPrimitiveType.Int32 || currentType == InputPrimitiveType.Float32) + throw new JsonException($"Enum value types are not consistent."); + currentType = InputPrimitiveType.String; + break; + default: + throw new JsonException($"Unsupported enum value type, expect string, int or float."); + } + } + valueType = currentType ?? throw new JsonException("Enum value type must be set."); + + var enumType = new InputEnumType(name, ns, accessibility, deprecated, description, usage, valueType, NormalizeValues(allowedValues, valueType), isExtendable, isNullable); + if (id != null) + { + resolver.AddReference(id, enumType); + } + return enumType; + } + + private static IReadOnlyList NormalizeValues(IReadOnlyList allowedValues, InputPrimitiveType valueType) + { + var concreteValues = new List(allowedValues.Count); + + switch (valueType.Kind) + { + case InputTypeKind.String: + foreach (var value in allowedValues) + { + concreteValues.Add(new InputEnumTypeStringValue(value.Name, (string)value.Value, value.Description)); + } + break; + case InputTypeKind.Int32: + foreach (var value in allowedValues) + { + concreteValues.Add(new InputEnumTypeIntegerValue(value.Name, (int)value.Value, value.Description)); + } + break; + case InputTypeKind.Float32: + foreach (var value in allowedValues) + { + switch (value.Value) + { + case int i: + concreteValues.Add(new InputEnumTypeFloatValue(value.Name, i, value.Description)); + break; + case float f: + concreteValues.Add(new InputEnumTypeFloatValue(value.Name, f, value.Description)); + break; + default: + throw new JsonException($"Enum value type of ${value.Name} cannot cast to float."); + } + } + break; + default: + throw new JsonException($"Unsupported enum value type: {valueType.Kind}"); + } + + return concreteValues; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputEnumTypeValueConverter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputEnumTypeValueConverter.cs new file mode 100644 index 0000000000..1b25637540 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputEnumTypeValueConverter.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal sealed class TypeSpecInputEnumTypeValueConverter : JsonConverter + { + private readonly TypeSpecReferenceHandler _referenceHandler; + + public TypeSpecInputEnumTypeValueConverter(TypeSpecReferenceHandler referenceHandler) + { + _referenceHandler = referenceHandler; + } + + public override InputEnumTypeValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => reader.ReadReferenceAndResolve(_referenceHandler.CurrentResolver) ?? CreateEnumTypeValue(ref reader, null, null, _referenceHandler.CurrentResolver); + + public override void Write(Utf8JsonWriter writer, InputEnumTypeValue value, JsonSerializerOptions options) + => throw new NotSupportedException("Writing not supported"); + + public static InputEnumTypeValue CreateEnumTypeValue(ref Utf8JsonReader reader, string? id, string? name, ReferenceResolver resolver) + { + var isFirstProperty = id == null; + object? value = null; + string? description = null; + while (reader.TokenType != JsonTokenType.EndObject) + { + var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadString(nameof(InputEnumTypeValue.Name), ref name) + || reader.TryReadEnumValue(nameof(InputEnumTypeValue.Value), ref value) + || reader.TryReadString(nameof(InputEnumTypeValue.Description), ref description); + + if (!isKnownProperty) + { + reader.SkipProperty(); + } + } + + name = name ?? throw new JsonException("EnumValue must have name"); + + value = value ?? throw new JsonException("EnumValue must have value"); + + var enumValue = new InputEnumTypeValue(name, value, description); + if (id != null) + { + resolver.AddReference(id, enumValue); + } + return enumValue; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputListTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputListTypeConverter.cs new file mode 100644 index 0000000000..6968be9974 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputListTypeConverter.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal sealed class TypeSpecInputListTypeConverter : JsonConverter + { + private readonly TypeSpecReferenceHandler _referenceHandler; + + public TypeSpecInputListTypeConverter(TypeSpecReferenceHandler referenceHandler) + { + _referenceHandler = referenceHandler; + } + + public override InputList? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => reader.ReadReferenceAndResolve(_referenceHandler.CurrentResolver) ?? CreateListType(ref reader, null, null, options, _referenceHandler.CurrentResolver); + + public override void Write(Utf8JsonWriter writer, InputList value, JsonSerializerOptions options) + => throw new NotSupportedException("Writing not supported"); + + public static InputList CreateListType(ref Utf8JsonReader reader, string? id, string? name, JsonSerializerOptions options, ReferenceResolver resolver) + { + var isFirstProperty = id == null && name == null; + bool isNullable = false; + InputType? elementType = null; + while (reader.TokenType != JsonTokenType.EndObject) + { + var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadString(nameof(InputList.Name), ref name) + || reader.TryReadBoolean(nameof(InputList.IsNullable), ref isNullable) + || reader.TryReadWithConverter(nameof(InputList.ElementType), options, ref elementType); + + if (!isKnownProperty) + { + reader.SkipProperty(); + } + } + + elementType = elementType ?? throw new JsonException("List must have element type"); + var listType = new InputList(name ?? "List", elementType, false, isNullable); + if (id != null) + { + resolver.AddReference(id, listType); + } + return listType; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputLiteralTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputLiteralTypeConverter.cs new file mode 100644 index 0000000000..1a7805edb2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputLiteralTypeConverter.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal class TypeSpecInputLiteralTypeConverter : JsonConverter + { + private readonly TypeSpecReferenceHandler _referenceHandler; + public TypeSpecInputLiteralTypeConverter(TypeSpecReferenceHandler referenceHandler) + { + _referenceHandler = referenceHandler; + } + + public override InputLiteralType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => reader.ReadReferenceAndResolve(_referenceHandler.CurrentResolver) ?? CreateInputLiteralType(ref reader, null, null, options, _referenceHandler.CurrentResolver); + + public override void Write(Utf8JsonWriter writer, InputLiteralType value, JsonSerializerOptions options) + => throw new NotSupportedException("Writing not supported"); + + public static InputLiteralType CreateInputLiteralType(ref Utf8JsonReader reader, string? id, string? name, JsonSerializerOptions options, ReferenceResolver resolver) + { + var isFirstProperty = id == null && name == null; + bool isNullable = false; + object? value = null; + InputType? type = null; + + while (reader.TokenType != JsonTokenType.EndObject) + { + var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadString(nameof(InputLiteralType.Name), ref name) + || reader.TryReadBoolean(nameof(InputLiteralType.IsNullable), ref isNullable) + || reader.TryReadWithConverter(nameof(InputLiteralType.LiteralValueType), options, ref type); + + if (isKnownProperty) + { + continue; + } + + if (reader.GetString() == nameof(InputLiteralType.Value)) + { + value = ReadLiteralValue(ref reader, nameof(InputLiteralType.Value), options, type); + } + else + { + reader.SkipProperty(); + } + } + + name = name ?? throw new JsonException($"{nameof(InputLiteralType)} must have a name."); + + type = type ?? throw new JsonException("InputConstant must have type"); + + value = value ?? throw new JsonException("InputConstant must have value"); + + var literalType = new InputLiteralType(name, type, value, isNullable); + + if (id != null) + { + resolver.AddReference(id, literalType); + } + return literalType; + } + + public static object ReadLiteralValue(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, InputType? type) + { + if (type == null) + { + throw new JsonException("Must place ValueType ahead of value."); + } + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + if (reader.GetString() != propertyName) + { + throw new JsonException("This is not for json field " + propertyName); + } + + reader.Read(); + // get the kind of the primitive type or the underlying type of the enum + var kind = type switch + { + InputPrimitiveType primitiveType => primitiveType.Kind, + InputEnumType enumType => enumType.EnumValueType.Kind, + _ => throw new JsonException($"Not supported literal type {type.GetType()}.") + }; + object value = kind switch + { + InputTypeKind.String => reader.GetString() ?? throw new JsonException(), + InputTypeKind.Int32 => reader.GetInt32(), + InputTypeKind.Float32 => reader.GetSingle(), + InputTypeKind.Float64 => reader.GetDouble(), + InputTypeKind.Boolean => reader.GetBoolean(), + _ => throw new JsonException($"Not supported literal type {kind}.") + }; + reader.Read(); + return value; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputModelPropertyConverter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputModelPropertyConverter.cs new file mode 100644 index 0000000000..b4193bcb83 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputModelPropertyConverter.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal sealed class TypeSpecInputModelPropertyConverter : JsonConverter + { + private readonly TypeSpecReferenceHandler _referenceHandler; + + public TypeSpecInputModelPropertyConverter(TypeSpecReferenceHandler referenceHandler) + { + _referenceHandler = referenceHandler; + } + + public override InputModelProperty Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => reader.ReadReferenceAndResolve(_referenceHandler.CurrentResolver) ?? ReadInputModelProperty(ref reader, null, null, options, _referenceHandler.CurrentResolver); + + public override void Write(Utf8JsonWriter writer, InputModelProperty value, JsonSerializerOptions options) + => throw new NotSupportedException("Writing not supported"); + + private static InputModelProperty ReadInputModelProperty(ref Utf8JsonReader reader, string? id, string? name, JsonSerializerOptions options, ReferenceResolver resolver) + { + var isFirstProperty = true; + string? serializedName = null; + string? description = null; + InputType? propertyType = null; + bool isReadOnly = false; + bool isRequired = false; + bool isDiscriminator = false; + + while (reader.TokenType != JsonTokenType.EndObject) + { + var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadString(nameof(InputModelProperty.Name), ref name) + || reader.TryReadString(nameof(InputModelProperty.SerializedName), ref serializedName) + || reader.TryReadString(nameof(InputModelProperty.Description), ref description) + || reader.TryReadWithConverter(nameof(InputModelProperty.Type), options, ref propertyType) + || reader.TryReadBoolean(nameof(InputModelProperty.IsReadOnly), ref isReadOnly) + || reader.TryReadBoolean(nameof(InputModelProperty.IsRequired), ref isRequired) + || reader.TryReadBoolean(nameof(InputModelProperty.IsDiscriminator), ref isDiscriminator); + + if (!isKnownProperty) + { + reader.SkipProperty(); + } + } + + name = name ?? throw new JsonException($"{nameof(InputModelProperty)} must have a name."); + description = description ?? throw new JsonException($"{nameof(InputModelProperty)} must have a description."); + // TO-DO: Implement as part of autorest output classes migration https://github.com/Azure/autorest.csharp/issues/4198 + // description = BuilderHelpers.EscapeXmlDocDescription(description); + propertyType = propertyType ?? throw new JsonException($"{nameof(InputModelProperty)} must have a property type."); + + var property = new InputModelProperty(name, serializedName ?? name, description, propertyType, isRequired, isReadOnly, isDiscriminator); + if (id != null) + { + resolver.AddReference(id, property); + } + + return property; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputModelTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputModelTypeConverter.cs new file mode 100644 index 0000000000..f611fbef53 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputModelTypeConverter.cs @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal sealed class TypeSpecInputModelTypeConverter : JsonConverter + { + private readonly TypeSpecReferenceHandler _referenceHandler; + + public TypeSpecInputModelTypeConverter(TypeSpecReferenceHandler referenceHandler) + { + _referenceHandler = referenceHandler; + } + + public override InputModelType? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => ReadModelType(ref reader, options, _referenceHandler.CurrentResolver); + + public override void Write(Utf8JsonWriter writer, InputModelType value, JsonSerializerOptions options) + => throw new NotSupportedException("Writing not supported"); + + private static InputModelType? ReadModelType(ref Utf8JsonReader reader, JsonSerializerOptions options, ReferenceResolver resolver) + => reader.ReadReferenceAndResolve(resolver) ?? CreateModelType(ref reader, null, null, options, resolver); + + public static InputModelType CreateModelType(ref Utf8JsonReader reader, string? id, string? name, JsonSerializerOptions options, ReferenceResolver resolver) + { + var isFirstProperty = id == null && name == null; + var properties = new List(); + bool isNullable = false; + string? ns = null; + string? accessibility = null; + string? deprecated = null; + string? description = null; + string? usageString = null; + string? discriminatorPropertyName = null; + string? discriminatorValue = null; + InputDictionary? inheritedDictionaryType = null; + InputModelType? baseModel = null; + InputModelType? model = null; + while (reader.TokenType != JsonTokenType.EndObject) + { + var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadString(nameof(InputModelType.Name), ref name) + || reader.TryReadBoolean(nameof(InputModelType.IsNullable), ref isNullable) + || reader.TryReadString(nameof(InputModelType.Namespace), ref ns) + || reader.TryReadString(nameof(InputModelType.Accessibility), ref accessibility) + || reader.TryReadString(nameof(InputModelType.Deprecated), ref deprecated) + || reader.TryReadString(nameof(InputModelType.Description), ref description) + || reader.TryReadString(nameof(InputModelType.Usage), ref usageString) + || reader.TryReadString(nameof(InputModelType.DiscriminatorPropertyName), ref discriminatorPropertyName) + || reader.TryReadString(nameof(InputModelType.DiscriminatorValue), ref discriminatorValue) + || reader.TryReadWithConverter(nameof(InputModelType.InheritedDictionaryType), options, ref inheritedDictionaryType); + + if (isKnownProperty) + { + continue; + } + /** + * If the model has base model, `BaseModel` and `Properties` should be the last two items in tspCodeModel. + * and `BaseModel` should be last but one, and `Properties` should be the last one. + */ + if (reader.GetString() == nameof(InputModelType.BaseModel)) + { + model = CreateInputModelTypeInstance(id, name, ns, accessibility, deprecated, description, usageString, discriminatorValue, discriminatorPropertyName, baseModel, properties, inheritedDictionaryType, isNullable, resolver); + reader.TryReadWithConverter(nameof(InputModelType.BaseModel), options, ref baseModel); + if (baseModel != null) + { + model.SetBaseModel(baseModel); + var baseModelDerived = (List)resolver.ResolveReference($"{baseModel.Name}.{nameof(InputModelType.DerivedModels)}"); + baseModelDerived.Add(model); + } + continue; + } + if (reader.GetString() == nameof(InputModelType.Properties)) + { + model = model ?? CreateInputModelTypeInstance(id, name, ns, accessibility, deprecated, description, usageString, discriminatorValue, discriminatorPropertyName, baseModel, properties, inheritedDictionaryType, isNullable, resolver); + reader.Read(); + CreateProperties(ref reader, properties, options); + if (reader.TokenType != JsonTokenType.EndObject) + { + throw new JsonException($"{nameof(InputModelType)}.{nameof(InputModelType.Properties)} must be the last defined property."); + } + } + else + { + reader.SkipProperty(); + } + } + + return model ?? CreateInputModelTypeInstance(id, name, ns, accessibility, deprecated, description, usageString, discriminatorValue, discriminatorPropertyName, baseModel, properties, inheritedDictionaryType, isNullable, resolver); + } + + private static InputModelType CreateInputModelTypeInstance(string? id, string? name, string? ns, string? accessibility, string? deprecated, string? description, string? usageString, string? discriminatorValue, string? discriminatorPropertyValue, InputModelType? baseModel, IReadOnlyList properties, InputDictionary? inheritedDictionaryType, bool isNullable, ReferenceResolver resolver) + { + name = name ?? throw new JsonException("Model must have name"); + InputModelTypeUsage usage = InputModelTypeUsage.None; + if (usageString != null) + { + Enum.TryParse(usageString, ignoreCase: true, out usage); + } + + var derivedModels = new List(); + var model = new InputModelType(name, ns, accessibility, deprecated, description, usage, properties, baseModel, derivedModels, discriminatorValue, discriminatorPropertyValue, inheritedDictionaryType, isNullable: isNullable); + + if (id is not null) + { + resolver.AddReference(id, model); + resolver.AddReference($"{model.Name}.{nameof(InputModelType.DerivedModels)}", derivedModels); + } + + if (baseModel is not null) + { + var baseModelDerived = (List)resolver.ResolveReference($"{baseModel.Name}.{nameof(InputModelType.DerivedModels)}"); + baseModelDerived.Add(model); + } + + return model; + } + + private static void CreateProperties(ref Utf8JsonReader reader, ICollection properties, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + throw new JsonException(); + } + reader.Read(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + var property = reader.ReadWithConverter(options); + properties.Add(property ?? throw new JsonException($"null {nameof(InputModelProperty)} is not allowed")); + } + reader.Read(); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputParameterConverter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputParameterConverter.cs new file mode 100644 index 0000000000..c25971107b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputParameterConverter.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal sealed class TypeSpecInputParameterConverter : JsonConverter + { + private readonly TypeSpecReferenceHandler _referenceHandler; + + public TypeSpecInputParameterConverter(TypeSpecReferenceHandler referenceHandler) + { + _referenceHandler = referenceHandler; + } + + public override InputParameter? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => ReadInputParameter(ref reader, options, _referenceHandler.CurrentResolver); + + public override void Write(Utf8JsonWriter writer, InputParameter value, JsonSerializerOptions options) + => throw new NotSupportedException("Writing not supported"); + + private static InputParameter? ReadInputParameter(ref Utf8JsonReader reader, JsonSerializerOptions options, ReferenceResolver resolver) + => reader.ReadReferenceAndResolve(resolver) ?? CreateInputParameter(ref reader, null, null, options, resolver); + + public static InputParameter CreateInputParameter(ref Utf8JsonReader reader, string? id, string? name, JsonSerializerOptions options, ReferenceResolver resolver) + { + var isFirstProperty = id == null && name == null; + + string? nameInRequest = null; + string? description = null; + InputType? parameterType = null; + string? location = null; + InputConstant? defaultValue = null; + InputParameter? groupBy = null; + string? kind = null; + bool isRequired = false; + bool isApiVersion = false; + bool isResourceParameter = false; + bool isContentType = false; + bool isEndpoint = false; + bool skipUrlEncoding = false; + bool explode = false; + string? arraySerializationDelimiter = null; + string? headerCollectionPrefix = null; + while (reader.TokenType != JsonTokenType.EndObject) + { + var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadString(nameof(InputParameter.Name), ref name) + || reader.TryReadString(nameof(InputParameter.NameInRequest), ref nameInRequest) + || reader.TryReadString(nameof(InputParameter.Description), ref description) + || reader.TryReadWithConverter(nameof(InputParameter.Type), options, ref parameterType) + || reader.TryReadString(nameof(InputParameter.Location), ref location) + || reader.TryReadWithConverter(nameof(InputParameter.DefaultValue), options, ref defaultValue) + || reader.TryReadWithConverter(nameof(InputParameter.GroupedBy), options, ref groupBy) + || reader.TryReadString(nameof(InputParameter.Kind), ref kind) + || reader.TryReadBoolean(nameof(InputParameter.IsRequired), ref isRequired) + || reader.TryReadBoolean(nameof(InputParameter.IsApiVersion), ref isApiVersion) + || reader.TryReadBoolean(nameof(InputParameter.IsResourceParameter), ref isResourceParameter) + || reader.TryReadBoolean(nameof(InputParameter.IsContentType), ref isContentType) + || reader.TryReadBoolean(nameof(InputParameter.IsEndpoint), ref isEndpoint) + || reader.TryReadBoolean(nameof(InputParameter.SkipUrlEncoding), ref skipUrlEncoding) + || reader.TryReadBoolean(nameof(InputParameter.Explode), ref explode) + || reader.TryReadString(nameof(InputParameter.ArraySerializationDelimiter), ref arraySerializationDelimiter) + || reader.TryReadString(nameof(InputParameter.HeaderCollectionPrefix), ref headerCollectionPrefix); + + if (!isKnownProperty) + { + reader.SkipProperty(); + } + } + + name = name ?? throw new JsonException("Parameter must have name"); + nameInRequest = nameInRequest ?? throw new JsonException("Parameter must have nameInRequest"); + parameterType = parameterType ?? throw new JsonException("Parameter must have type"); + + if (location == null) + { + throw new JsonException("Parameter must have location"); + } + Enum.TryParse(location, ignoreCase: true, out var requestLocation); + + if (kind == null) + { + throw new JsonException("Parameter must have kind"); + } + Enum.TryParse(kind, ignoreCase: true, out var parameterKind); + + var parameter = new InputParameter( + name: name, + nameInRequest: nameInRequest, + description: description, + type: parameterType, + location: requestLocation, + defaultValue: defaultValue, + groupedBy: groupBy, + kind: parameterKind, + isRequired: isRequired, + isApiVersion: isApiVersion, + isResourceParameter: isResourceParameter, + isContentType: isContentType, + isEndpoint: isEndpoint, + skipUrlEncoding: skipUrlEncoding, + explode: explode, + arraySerializationDelimiter: arraySerializationDelimiter, + headerCollectionPrefix: headerCollectionPrefix); + + if (id != null) + { + resolver.AddReference(id, parameter); + } + + return parameter; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputTypeConverter.cs new file mode 100644 index 0000000000..014bcac874 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputTypeConverter.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal sealed class TypeSpecInputTypeConverter : JsonConverter + { + private const string KindPropertyName = "Kind"; + + private readonly TypeSpecReferenceHandler _referenceHandler; + + public TypeSpecInputTypeConverter(TypeSpecReferenceHandler referenceHandler) + { + _referenceHandler = referenceHandler; + } + + public override InputType? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + return CreatePrimitiveType(reader.GetString(), false); + } + + return reader.ReadReferenceAndResolve(_referenceHandler.CurrentResolver) ?? CreateObject(ref reader, options); + } + + public override void Write(Utf8JsonWriter writer, InputType value, JsonSerializerOptions options) + => throw new NotSupportedException("Writing not supported"); + + private InputType CreateObject(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + string? id = null; + string? kind = null; + string? name = null; + InputType? result = null; + var isFirstProperty = true; + while (reader.TokenType != JsonTokenType.EndObject) + { + var isIdOrNameOrKind = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadString(KindPropertyName, ref kind) + || reader.TryReadString(nameof(InputType.Name), ref name); + + if (isIdOrNameOrKind) + { + continue; + } + result = CreateDerivedType(ref reader, id, kind, name, options); + } + + return result ?? throw new JsonException("cannot deserialize InputType"); + } + + private const string PrimitiveKind = "Primitive"; + private const string LiteralKind = "Literal"; + private const string UnionKind = "Union"; + private const string ModelKind = "Model"; + private const string EnumKind = "Enum"; + private const string ArrayKind = "Array"; + private const string DictionaryKind = "Dictionary"; + private const string IntrinsicKind = "Intrinsic"; + + private InputType CreateDerivedType(ref Utf8JsonReader reader, string? id, string? kind, string? name, JsonSerializerOptions options) => kind switch + { + PrimitiveKind => ReadPrimitiveType(ref reader, id, name, _referenceHandler.CurrentResolver), + LiteralKind => TypeSpecInputLiteralTypeConverter.CreateInputLiteralType(ref reader, id, name, options, _referenceHandler.CurrentResolver), + UnionKind => TypeSpecInputUnionTypeConverter.CreateInputUnionType(ref reader, id, name, options, _referenceHandler.CurrentResolver), + ModelKind => TypeSpecInputModelTypeConverter.CreateModelType(ref reader, id, name, options, _referenceHandler.CurrentResolver), + EnumKind => TypeSpecInputEnumTypeConverter.CreateEnumType(ref reader, id, name, options, _referenceHandler.CurrentResolver), + ArrayKind => TypeSpecInputListTypeConverter.CreateListType(ref reader, id, name, options, _referenceHandler.CurrentResolver), + DictionaryKind => TypeSpecInputDictionaryTypeConverter.CreateDictionaryType(ref reader, id, name, options, _referenceHandler.CurrentResolver), + IntrinsicKind => ReadIntrinsicType(ref reader, id, name, _referenceHandler.CurrentResolver), + null => throw new JsonException("InputType must have a 'Kind' property"), + _ => throw new JsonException($"unknown kind {kind}") + }; + + public static InputPrimitiveType ReadPrimitiveType(ref Utf8JsonReader reader, string? id, string? name, ReferenceResolver resolver) + { + var isFirstProperty = id == null; + var isNullable = false; + while (reader.TokenType != JsonTokenType.EndObject) + { + var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadBoolean(nameof(InputPrimitiveType.IsNullable), ref isNullable) + || reader.TryReadString(nameof(InputPrimitiveType.Name), ref name); // the primitive kind in the json is represented by the property `Name`. + + if (!isKnownProperty) + { + reader.SkipProperty(); + } + } + + var primitiveType = CreatePrimitiveType(name, isNullable); + if (id != null) + { + resolver.AddReference(id, primitiveType); + } + + return primitiveType; + } + + public static InputPrimitiveType CreatePrimitiveType(string? inputTypeKindString, bool isNullable) + { + ArgumentNullException.ThrowIfNull(inputTypeKindString, nameof(inputTypeKindString)); + return Enum.TryParse(inputTypeKindString, ignoreCase: true, out var kind) + ? new InputPrimitiveType(kind, isNullable) + : throw new JsonException($"{inputTypeKindString} type is unknown."); + } + + private static InputIntrinsicType ReadIntrinsicType(ref Utf8JsonReader reader, string? id, string? name, ReferenceResolver resolver) + { + var isFirstProperty = id == null; + while (reader.TokenType != JsonTokenType.EndObject) + { + var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadString(nameof(InputIntrinsicType.Kind), ref name); // the InputIntrinsicType kind in the json is represented by the property `Name`. + + if (!isKnownProperty) + { + reader.SkipProperty(); + } + } + + var intrinsicType = CreateIntrinsicType(name); + if (id != null) + { + resolver.AddReference(id, intrinsicType); + } + + return intrinsicType; + } + + private static InputIntrinsicType CreateIntrinsicType(string? inputTypeKindString) + { + ArgumentNullException.ThrowIfNull(inputTypeKindString, nameof(inputTypeKindString)); + return Enum.TryParse(inputTypeKindString, ignoreCase: true, out var kind) + ? new InputIntrinsicType(kind) + : throw new InvalidOperationException($"{inputTypeKindString} type is unknown for InputIntrinsicType."); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputUnionTypeConverter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputUnionTypeConverter.cs new file mode 100644 index 0000000000..d7203f812d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecInputUnionTypeConverter.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal class TypeSpecInputUnionTypeConverter : JsonConverter + { + private readonly TypeSpecReferenceHandler _referenceHandler; + public TypeSpecInputUnionTypeConverter(TypeSpecReferenceHandler referenceHandler) + { + _referenceHandler = referenceHandler; + } + + public override InputUnionType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => reader.ReadReferenceAndResolve(_referenceHandler.CurrentResolver) ?? CreateInputUnionType(ref reader, null, null, options, _referenceHandler.CurrentResolver); + + public override void Write(Utf8JsonWriter writer, InputUnionType value, JsonSerializerOptions options) + => throw new NotSupportedException("Writing not supported"); + + public static InputUnionType CreateInputUnionType(ref Utf8JsonReader reader, string? id, string? name, JsonSerializerOptions options, ReferenceResolver resolver) + { + var isFirstProperty = id == null; + bool isNullable = false; + var unionItemTypes = new List(); + while (reader.TokenType != JsonTokenType.EndObject) + { + var isKnownProperty = reader.TryReadReferenceId(ref isFirstProperty, ref id) + || reader.TryReadString(nameof(InputUnionType.Name), ref name) + || reader.TryReadBoolean(nameof(InputUnionType.IsNullable), ref isNullable); + + if (isKnownProperty) + { + continue; + } + + if (reader.GetString() == nameof(InputUnionType.UnionItemTypes)) + { + reader.Read(); + CreateUnionItemTypes(ref reader, unionItemTypes, options); + } + else + { + reader.SkipProperty(); + } + } + + name = name ?? throw new JsonException($"{nameof(InputLiteralType)} must have a name."); + if (unionItemTypes == null || unionItemTypes.Count == 0) + { + throw new JsonException("Union must have a least one union type"); + } + + var unionType = new InputUnionType(name, unionItemTypes, isNullable); + if (id != null) + { + resolver.AddReference(id, unionType); + } + return unionType; + } + + private static void CreateUnionItemTypes(ref Utf8JsonReader reader, ICollection itemTypes, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + throw new JsonException(); + } + reader.Read(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + var type = reader.ReadWithConverter(options); + itemTypes.Add(type ?? throw new JsonException($"null {nameof(InputType)} isn't allowed")); + } + reader.Read(); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecReferenceHandler.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecReferenceHandler.cs new file mode 100644 index 0000000000..e20cb2cd79 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecReferenceHandler.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + /// + /// Custom reference handler that preserves the same instance of the resolver across multiple calls of the converters Read method + /// Required for the reference preservation to work with custom converters + /// + internal sealed class TypeSpecReferenceHandler : ReferenceHandler + { + public ReferenceResolver CurrentResolver { get; } = new TypeSpecReferenceResolver(); + + public override ReferenceResolver CreateResolver() => CurrentResolver; + + private class TypeSpecReferenceResolver : ReferenceResolver + { + private readonly Dictionary _referenceIdToObjectMap = new(); + + public override void AddReference(string referenceId, object value) + { + if (!_referenceIdToObjectMap.TryAdd(referenceId, value)) + { + throw new JsonException(); + } + } + + public override string GetReference(object value, out bool alreadyExists) + => throw new InvalidOperationException("JSON writing isn't supported"); + + public override object ResolveReference(string referenceId) + => _referenceIdToObjectMap.TryGetValue(referenceId, out object? value) ? value : throw new JsonException(); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecSerialization.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecSerialization.cs new file mode 100644 index 0000000000..1f54c81781 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/TypeSpecSerialization.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + public static class TypeSpecSerialization + { + public static InputNamespace? Deserialize(string json) + { + var referenceHandler = new TypeSpecReferenceHandler(); + var options = new JsonSerializerOptions + { + ReferenceHandler = referenceHandler, + AllowTrailingCommas = true + }; + + options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); + options.Converters.Add(new TypeSpecInputTypeConverter(referenceHandler)); + options.Converters.Add(new TypeSpecInputListTypeConverter(referenceHandler)); + options.Converters.Add(new TypeSpecInputDictionaryTypeConverter(referenceHandler)); + options.Converters.Add(new TypeSpecInputEnumTypeConverter(referenceHandler)); + options.Converters.Add(new TypeSpecInputEnumTypeValueConverter(referenceHandler)); + options.Converters.Add(new TypeSpecInputModelTypeConverter(referenceHandler)); + options.Converters.Add(new TypeSpecInputModelPropertyConverter(referenceHandler)); + options.Converters.Add(new TypeSpecInputConstantConverter(referenceHandler)); + options.Converters.Add(new TypeSpecInputLiteralTypeConverter(referenceHandler)); + options.Converters.Add(new TypeSpecInputUnionTypeConverter(referenceHandler)); + options.Converters.Add(new TypeSpecInputParameterConverter(referenceHandler)); + return JsonSerializer.Deserialize(json, options); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/Utf8JsonReaderExtensions.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/Utf8JsonReaderExtensions.cs new file mode 100644 index 0000000000..2435f7bb80 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/InputTypes/Serialization/Utf8JsonReaderExtensions.cs @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.Generator.CSharp.Input +{ + internal static class Utf8JsonReaderExtensions + { + public static bool TryReadReferenceId(this ref Utf8JsonReader reader, ref bool isFirstProperty, ref string? value) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + if (reader.GetString() != "$id") + { + return false; + } + + if (!isFirstProperty) + { + throw new JsonException("$id should be the first defined property"); + } + + isFirstProperty = false; + + reader.Read(); + value = reader.GetString() ?? throw new JsonException(); + reader.Read(); + return true; + } + + public static bool TryReadBoolean(this ref Utf8JsonReader reader, string propertyName, ref bool value) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + if (reader.GetString() != propertyName) + { + return false; + } + + reader.Read(); + value = reader.GetBoolean(); + reader.Read(); + return true; + } + + public static bool TryReadString(this ref Utf8JsonReader reader, string propertyName, ref string? value) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + if (reader.GetString() != propertyName) + { + return false; + } + + reader.Read(); + value = reader.GetString() ?? throw new JsonException(); + reader.Read(); + return true; + } + + public static bool TryReadEnumValue(this ref Utf8JsonReader reader, string propertyName, ref object? value) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + if (reader.GetString() != propertyName) + { + return false; + } + + reader.Read(); + switch (reader.TokenType) + { + case JsonTokenType.String: + value = reader.GetString() ?? throw new JsonException("Enum value cannot be empty"); + break; + case JsonTokenType.Number: + if (reader.TryGetInt32(out int intValue)) + { + value = intValue; + } + else if (reader.TryGetSingle(out float floatValue)) + { + value = floatValue; + } + else + { + throw new JsonException($"Unsupported enum value type: {reader.TokenType}"); + } + break; + default: + throw new JsonException($"Unsupported enum value type: {reader.TokenType}"); + } + + reader.Read(); + return true; + } + + public static bool TryReadPrimitiveType(this ref Utf8JsonReader reader, string propertyName, ref InputPrimitiveType? value) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + if (reader.GetString() != propertyName) + { + return false; + } + + reader.Read(); + value = TypeSpecInputTypeConverter.CreatePrimitiveType(reader.GetString(), false) ?? throw new JsonException(); + reader.Read(); + return true; + } + + public static bool TryReadWithConverter(this ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref T? value) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + if (reader.GetString() != propertyName) + { + return false; + } + + reader.Read(); + value = reader.ReadWithConverter(options); + return true; + } + + public static T? ReadWithConverter(this ref Utf8JsonReader reader, JsonSerializerOptions options) + { + var converter = (JsonConverter)options.GetConverter(typeof(T)); + var value = converter.Read(ref reader, typeof(T), options); + reader.Read(); + return value; + } + + public static T? ReadReferenceAndResolve(this ref Utf8JsonReader reader, ReferenceResolver resolver) where T : class + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + reader.Read(); + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + if (reader.GetString() != "$ref") + { + return null; + } + + reader.Read(); + var idRef = reader.GetString() ?? throw new JsonException("$ref can't be null"); + var result = (T)resolver.ResolveReference(idRef ?? throw new JsonException()); + + reader.Read(); + if (reader.TokenType != JsonTokenType.EndObject) + { + throw new JsonException("$ref should be the only property"); + } + + return result; + } + + public static void SkipProperty(this ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + reader.Read(); + reader.SkipValue(); + } + + private static void SkipValue(this ref Utf8JsonReader reader) + { + switch (reader.TokenType) + { + case JsonTokenType.StartObject: + reader.Read(); + while (reader.TokenType != JsonTokenType.EndObject) + { + reader.SkipProperty(); + } + reader.Read(); + break; + case JsonTokenType.StartArray: + reader.Read(); + while (reader.TokenType != JsonTokenType.EndArray) + { + reader.SkipValue(); + } + reader.Read(); + break; + case JsonTokenType.String: + case JsonTokenType.Number: + case JsonTokenType.True: + case JsonTokenType.False: + case JsonTokenType.Null: + reader.Read(); + break; + case JsonTokenType.Comment: + case JsonTokenType.None: + case JsonTokenType.EndObject: + case JsonTokenType.EndArray: + case JsonTokenType.PropertyName: + throw new InvalidOperationException("Unexpected token type"); + default: + throw new ArgumentOutOfRangeException(); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/Microsoft.Generator.CSharp.Input.csproj b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/Microsoft.Generator.CSharp.Input.csproj new file mode 100644 index 0000000000..96edbdc2cb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.Input/src/Microsoft.Generator.CSharp.Input.csproj @@ -0,0 +1,8 @@ + + + + Microsoft.Generator.CSharp.Input + net8.0 + enable + + diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.sln b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.sln new file mode 100644 index 0000000000..a1736fbe28 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34309.116 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Generator.CSharp.ClientModel", "Microsoft.Generator.CSharp.ClientModel\src\Microsoft.Generator.CSharp.ClientModel.csproj", "{D9290EE2-C497-417F-ACBA-3463DC83F3E1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Generator.CSharp", "Microsoft.Generator.CSharp\src\Microsoft.Generator.CSharp.csproj", "{63EEFF7B-A264-4D8A-84F0-E815E70275EB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Generator.CSharp.Tests", "Microsoft.Generator.CSharp\test\Microsoft.Generator.CSharp.Tests.csproj", "{BA8C1BC7-B5F5-456A-8FC9-1EA182B35420}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Generator.CSharp.Input", "Microsoft.Generator.CSharp.Input\src\Microsoft.Generator.CSharp.Input.csproj", "{B5484903-F77E-4C50-82C6-240AFECDF309}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Generator.CSharp.ClientModel.TestProjects", "Microsoft.Generator.CSharp.ClientModel.TestProjects\Microsoft.Generator.CSharp.ClientModel.TestProjects.csproj", "{2192EE77-150D-425D-B117-B13DF8794BC6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Generator.CSharp.Customization", "Microsoft.Generator.CSharp.Customization\src\Microsoft.Generator.CSharp.Customization.csproj", "{82508C25-0623-4EDE-A6FA-3DB395A5BB37}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{37E859E7-8CCE-426B-8232-35B2C1503230}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + readme.md = readme.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D9290EE2-C497-417F-ACBA-3463DC83F3E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9290EE2-C497-417F-ACBA-3463DC83F3E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9290EE2-C497-417F-ACBA-3463DC83F3E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9290EE2-C497-417F-ACBA-3463DC83F3E1}.Release|Any CPU.Build.0 = Release|Any CPU + {63EEFF7B-A264-4D8A-84F0-E815E70275EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63EEFF7B-A264-4D8A-84F0-E815E70275EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63EEFF7B-A264-4D8A-84F0-E815E70275EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63EEFF7B-A264-4D8A-84F0-E815E70275EB}.Release|Any CPU.Build.0 = Release|Any CPU + {BA8C1BC7-B5F5-456A-8FC9-1EA182B35420}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA8C1BC7-B5F5-456A-8FC9-1EA182B35420}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA8C1BC7-B5F5-456A-8FC9-1EA182B35420}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA8C1BC7-B5F5-456A-8FC9-1EA182B35420}.Release|Any CPU.Build.0 = Release|Any CPU + {B5484903-F77E-4C50-82C6-240AFECDF309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5484903-F77E-4C50-82C6-240AFECDF309}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5484903-F77E-4C50-82C6-240AFECDF309}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5484903-F77E-4C50-82C6-240AFECDF309}.Release|Any CPU.Build.0 = Release|Any CPU + {2192EE77-150D-425D-B117-B13DF8794BC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2192EE77-150D-425D-B117-B13DF8794BC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2192EE77-150D-425D-B117-B13DF8794BC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2192EE77-150D-425D-B117-B13DF8794BC6}.Release|Any CPU.Build.0 = Release|Any CPU + {82508C25-0623-4EDE-A6FA-3DB395A5BB37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82508C25-0623-4EDE-A6FA-3DB395A5BB37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82508C25-0623-4EDE-A6FA-3DB395A5BB37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82508C25-0623-4EDE-A6FA-3DB395A5BB37}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {89FD67DC-77D0-4B92-92B3-54392212AFB9} + EndGlobalSection +EndGlobal diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/ApiTypes.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/ApiTypes.cs new file mode 100644 index 0000000000..67a616b910 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/ApiTypes.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp +{ + /// + /// ApiTypes represent the API types used in the generated code. + /// + public abstract class ApiTypes + { + /// + /// The type for change tracking lists. + /// + public abstract Type ChangeTrackingListType { get; } + /// + /// The type for change tracking dictionaries. + /// + public abstract Type ChangeTrackingDictionaryType { get; } + + /// + /// The sample value for the endpoint. + /// + public abstract string EndPointSampleValue { get; } + + // TO-DO: Refactor this to use overridable properties in expression / statements: https://github.com/Azure/autorest.csharp/issues/4226 + public abstract Type ResponseType { get; } + public abstract Type ResponseOfTType { get; } + + public string FromResponseName => "FromResponse"; + public abstract string ResponseParameterName { get; } + public abstract string ContentStreamName { get; } + public abstract string StatusName { get; } + + public abstract string GetRawResponseName { get; } + + public Type GetResponseOfT() => ResponseOfTType.MakeGenericType(typeof(T)); + public Type GetTaskOfResponse(Type? valueType = default) => + valueType is null ? typeof(Task<>).MakeGenericType(ResponseType) : typeof(Task<>).MakeGenericType(ResponseOfTType.MakeGenericType(valueType)); + public Type GetValueTaskOfResponse(Type? valueType = default) => + valueType is null ? typeof(ValueTask<>).MakeGenericType(ResponseType) : typeof(ValueTask<>).MakeGenericType(ResponseOfTType.MakeGenericType(valueType)); + + public abstract Type HttpPipelineType { get; } + public abstract Type PipelineExtensionsType { get; } + public abstract string HttpPipelineCreateMessageName { get; } + public FormattableString GetHttpPipelineCreateMessageFormat(bool withContext) + { + FormattableString context = withContext ? (FormattableString)$"{KnownParameters.RequestContext.Name:I}" : $""; + return $"_pipeline.{CodeModelPlugin.Instance.Configuration.ApiTypes.HttpPipelineCreateMessageName}({context}"; + } + + public abstract Type HttpMessageType { get; } + public abstract string HttpMessageResponseName { get; } + public abstract string HttpMessageResponseStatusName { get; } + + public Type GetNextPageFuncType() => typeof(Func<,,>).MakeGenericType(typeof(int?), typeof(string), HttpMessageType); + + public abstract Type ClientDiagnosticsType { get; } + public abstract string ClientDiagnosticsCreateScopeName { get; } + + public abstract Type ClientOptionsType { get; } + + public abstract Type RequestContextType { get; } + public abstract string CancellationTokenName { get; } + + public abstract Type HttpPipelineBuilderType { get; } + public abstract Type BearerAuthenticationPolicyType { get; } + public abstract Type KeyCredentialPolicyType { get; } + public abstract Type KeyCredentialType { get; } + public abstract FormattableString GetHttpPipelineBearerString(string pipelineField, string optionsVariable, string credentialVariable, string scopesParamName); + public FormattableString GetHttpPipelineKeyCredentialString(string pipelineField, string optionsVariable, string credentialVariable, string keyName) + => $"{pipelineField} = {HttpPipelineBuilderType}.Build({optionsVariable}, new {KeyCredentialPolicyType}({credentialVariable}, \"{keyName}\"));"; + public abstract FormattableString GetHttpPipelineClassifierString(string pipelineField, string optionsVariable, FormattableString perCallPolicies, FormattableString perRetryPolicies); + + public abstract Type HttpPipelinePolicyType { get; } + public abstract string HttpMessageRequestName { get; } + + public abstract FormattableString GetSetMethodString(string requestName, string method); + public abstract FormattableString GetSetUriString(string requestName, string uriName); + + // TO-DO: migrate as part of autorest output types migration: https://github.com/Azure/autorest.csharp/issues/4198 + // public abstract Action WriteHeaderMethod { get; } + + public abstract FormattableString GetSetContentString(string requestName, string contentName); + public abstract Type RequestUriType { get; } + public abstract Type RequestContentType { get; } + public abstract string ToRequestContentName { get; } + public abstract string RequestContentCreateName { get; } + + public abstract Type IUtf8JsonSerializableType { get; } + public abstract Type IXmlSerializableType { get; } + public abstract string IUtf8JsonSerializableWriteName { get; } + + public abstract Type Utf8JsonWriterExtensionsType { get; } + public abstract string Utf8JsonWriterExtensionsWriteObjectValueName { get; } + public abstract string Utf8JsonWriterExtensionsWriteNumberValueName { get; } + public abstract string Utf8JsonWriterExtensionsWriteStringValueName { get; } + public abstract string Utf8JsonWriterExtensionsWriteBase64StringValueName { get; } + + public abstract Type OptionalType { get; } + public abstract Type OptionalPropertyType { get; } + + public abstract string OptionalIsCollectionDefinedName { get; } + public abstract string OptionalIsDefinedName { get; } + public abstract string OptionalToDictionaryName { get; } + public abstract string OptionalToListName { get; } + public abstract string OptionalToNullableName { get; } + + public abstract Type RequestFailedExceptionType { get; } + + public abstract string ResponseClassifierIsErrorResponseName { get; } + + public abstract string JsonElementVariableName { get; } + public abstract Type ResponseClassifierType { get; } + public abstract Type StatusCodeClassifierType { get; } + public abstract ValueExpression GetCreateFromStreamSampleExpression(ValueExpression freeFormObjectExpression); + + public abstract ValueExpression GetKeySampleExpression(string clientName); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CSharpAttribute.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CSharpAttribute.cs new file mode 100644 index 0000000000..e0875a3e24 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CSharpAttribute.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp +{ + public sealed record CSharpAttribute(CSharpType Type, IReadOnlyList Arguments) + { + public CSharpAttribute(CSharpType type, params ValueExpression[] arguments) : this(type, (IReadOnlyList)arguments) { } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CSharpGen.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CSharpGen.cs new file mode 100644 index 0000000000..261979a658 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CSharpGen.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Generator.CSharp.Input; +using Microsoft.Generator.CSharp.Writers; + +namespace Microsoft.Generator.CSharp +{ + public sealed class CSharpGen + { + private static readonly string[] _filesToKeep = [Constants.DefaultCodeModelFileName, Constants.DefaultConfigurationFileName]; + + /// + /// Executes the generator task with the instance. + /// + public async Task ExecuteAsync() + { + GeneratedCodeWorkspace.Initialize(); + var outputPath = CodeModelPlugin.Instance.Configuration.OutputDirectory; + var generatedTestOutputPath = Path.Combine(outputPath, "..", "..", "tests", Constants.DefaultGeneratedCodeFolderName); + + var inputNamespace = await InputLibrary.Load(outputPath); + GeneratedCodeWorkspace workspace = await GeneratedCodeWorkspace.Create(); + + var output = CodeModelPlugin.Instance.GetOutputLibrary(inputNamespace); + Directory.CreateDirectory(Path.Combine(outputPath, "src", "Generated", "Models")); + List generateFilesTasks = new(); + foreach (var model in output.Models) + { + CodeWriter writer = new CodeWriter(); + ExpressionTypeProviderWriter modelWriter = CodeModelPlugin.Instance.GetExpressionTypeProviderWriter(writer, model); + modelWriter.Write(); + generateFilesTasks.Add(workspace.AddGeneratedFile(Path.Combine("src", "Generated", "Models", $"{model.Name}.cs"), writer.ToString())); + } + + // Add all the generated files to the workspace + await Task.WhenAll(generateFilesTasks); + + if (CodeModelPlugin.Instance.Configuration.ClearOutputFolder) + { + DeleteDirectory(outputPath, _filesToKeep); + DeleteDirectory(generatedTestOutputPath, _filesToKeep); + } + + // Write the generated files to the output directory + await foreach (var file in workspace.GetGeneratedFilesAsync()) + { + if (string.IsNullOrEmpty(file.Text)) + { + continue; + } + var filename = Path.Combine(outputPath, file.Name); + Console.WriteLine($"Writing {Path.GetFullPath(filename)}"); + Directory.CreateDirectory(Path.GetDirectoryName(filename)!); + await File.WriteAllTextAsync(filename, file.Text); + } + } + + /// + /// Parses and updates the output path for the generated code. + /// + /// The output path. + /// The parsed output path string. + internal static string ParseOutputPath(string outputPath) + { + if (!outputPath.EndsWith("src", StringComparison.Ordinal) && !outputPath.EndsWith("src/", StringComparison.Ordinal)) + { + outputPath = Path.Combine(outputPath, "src"); + } + + return outputPath; + } + + /// + /// Clears the output directory specified by . If is not null, + /// the specified files in the output directory will not be deleted. + /// + /// The path of the directory to delete. + /// The list of file names to retain. + private static void DeleteDirectory(string path, string[] filesToKeep) + { + var directoryInfo = new DirectoryInfo(path); + if (!directoryInfo.Exists) + { + return; + } + + foreach (var file in directoryInfo.GetFiles("*", SearchOption.AllDirectories)) + { + if (!filesToKeep.Contains(file.Name)) + { + file.Delete(); + } + } + + foreach (var directory in directoryInfo.GetDirectories("*", SearchOption.AllDirectories)) + { + if (!Directory.Exists(directory.FullName)) + { + continue; + } + + if (!directory.EnumerateFiles("*", SearchOption.AllDirectories).Any()) + { + directory.Delete(true); + } + } + + if (!directoryInfo.EnumerateFileSystemInfos().Any()) + { + directoryInfo.Delete(); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CSharpType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CSharpType.cs new file mode 100644 index 0000000000..8d3bfb088a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CSharpType.cs @@ -0,0 +1,589 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Microsoft.Generator.CSharp +{ + /// + /// CSharpType represents the C# type of an input type. + /// It is constructed from a and its properties. + /// + public class CSharpType + { + private readonly TypeProvider? _implementation; + private readonly Type? _type; + private string _name; + private string _namespace; + private CSharpType? _declaringType; + private bool _isValueType; + private bool _isEnum; + private bool _isNullable; + private bool _isPublic; + private bool? _isUnion; + private IReadOnlyList _arguments; + private IReadOnlyList _unionItemTypes; + + private bool? _isReadOnlyMemory; + private bool? _isList; + private bool? _isArray; + private bool? _isReadOnlyList; + private bool? _isReadWriteList; + private bool? _isDictionary; + private bool? _isReadOnlyDictionary; + private bool? _isReadWriteDictionary; + private bool? _isCollection; + private bool? _isIEnumerableOfT; + private bool? _isIAsyncEnumerableOfT; + private int? _hashCode; + private CSharpType? _initializationType; + private CSharpType? _propertyInitializationType; + private CSharpType? _elementType; + private CSharpType? _inputType; + private CSharpType? _outputType; + internal bool IsReadOnlyMemory => _isReadOnlyMemory ??= TypeIsReadOnlyMemory(); + internal bool IsList => _isList ??= TypeIsList(); + internal bool IsArray => _isArray ??= TypeIsArray(); + internal bool IsReadOnlyList => _isReadOnlyList ??= TypeIsReadOnlyList(); + internal bool IsReadWriteList => _isReadWriteList ??= TypeIsReadWriteList(); + internal bool IsDictionary => _isDictionary ??= TypeIsDictionary(); + internal bool IsReadOnlyDictionary => _isReadOnlyDictionary ??= TypeIsReadOnlyDictionary(); + internal bool IsReadWriteDictionary => _isReadWriteDictionary ??= TypeIsReadWriteDictionary(); + internal bool IsIEnumerableOfT => _isIEnumerableOfT ??= TypeIsIEnumerableOfT(); + internal bool IsIAsyncEnumerableOfT => _isIAsyncEnumerableOfT ??= TypeIsIAsyncEnumerableOfT(); + + /// + /// Constructs a from a . + /// + /// The base system type. + /// Optional flag to determine if the constructed type should be nullable. Defaults to false. + public CSharpType(Type type, bool isNullable = false) : this( + type, + isNullable, + type.IsGenericType ? type.GetGenericArguments().Select(p => new CSharpType(p)).ToArray() : Array.Empty()) + { } + + /// + /// Constructs a non-nullable from a with arguments + /// + /// The base system type. + /// The type's arguments. + public CSharpType(Type type, params CSharpType[] arguments) : this(type, arguments, false) + { } + + /// + /// Constructs a from a with arguments. + /// + /// The base system type. + /// Optional flag to determine if the constructed type should be nullable. Defaults to false. + /// The type's arguments. + public CSharpType(Type type, bool isNullable, params CSharpType[] arguments) : this(type, arguments, isNullable) + { } + + /// + /// Constructs a from a . + /// + /// The base system type. + /// The type's arguments. + /// Optional flag to determine if the constructed type should be nullable. Defaults to false. + public CSharpType(Type type, IReadOnlyList arguments, bool isNullable = false) + { + Debug.Assert(type.Namespace != null, "type.Namespace != null"); + + _type = type.IsGenericType ? type.GetGenericTypeDefinition() : type; + ValidateArguments(_type, arguments); + + var name = type.IsGenericType ? type.Name.Substring(0, type.Name.IndexOf('`')) : type.Name; + var isValueType = type.IsValueType; + var isEnum = type.IsEnum; + var ns = type.Namespace; + var isPublic = type.IsPublic && arguments.All(t => t.IsPublic); + // open generic parameter such as the `T` in `List` is considered as declared inside the `List` type as well, but we just want this to be the pure nested type, therefore here we exclude the open generic parameter scenario + // for a closed generic parameter such as the `string` in `List`, it is just an ordinary type without a `DeclaringType`. + var declaringType = type.DeclaringType is not null && !type.IsGenericParameter ? new CSharpType(type.DeclaringType) : null; + + Initialize(name, isValueType, isEnum, isNullable, ns, declaringType, arguments, isPublic); + } + + [Conditional("DEBUG")] + private static void ValidateArguments(Type type, IReadOnlyList arguments) + { + if (type.IsGenericTypeDefinition) + { + Debug.Assert(arguments.Count == type.GetGenericArguments().Length, $"the count of arguments given ({string.Join(", ", arguments.Select(a => a.ToString()))}) does not match the arguments in the definition {type}"); + } + else + { + Debug.Assert(arguments.Count == 0, "arguments can be added only to the generic type definition."); + } + } + + internal CSharpType(TypeProvider implementation, bool isValueType = false, bool isEnum = false, bool isNullable = false, IReadOnlyList? arguments = null, CSharpType? declaringType = null, string? ns = null, string? name = null) + { + _implementation = implementation; + _arguments = arguments ?? Array.Empty(); + + var isPublic = (implementation.DeclarationModifiers & TypeSignatureModifiers.Public) != 0 + && Arguments.All(t => t.IsPublic); + + Initialize(name, isValueType, isEnum, isNullable, ns, declaringType, arguments, isPublic); + + SerializeAs = _implementation?.SerializeAs; + } + + [MemberNotNull(nameof(_name))] + [MemberNotNull(nameof(_isValueType))] + [MemberNotNull(nameof(_isEnum))] + [MemberNotNull(nameof(_isNullable))] + [MemberNotNull(nameof(_namespace))] + [MemberNotNull(nameof(_arguments))] + [MemberNotNull(nameof(_isPublic))] + [MemberNotNull(nameof(_unionItemTypes))] + private void Initialize(string? name, bool isValueType, bool isEnum, bool isNullable, string? ns, + CSharpType? declaringType, IReadOnlyList? args, bool isPublic) + { + _name = name ?? string.Empty; + _isValueType = isValueType; + _isEnum = isEnum; + _isNullable = isNullable; + _namespace = ns ?? string.Empty; + _declaringType = declaringType; + _arguments = args ?? Array.Empty(); + _isPublic = isPublic; + _unionItemTypes ??= Array.Empty(); + } + + public string Namespace { get { return _namespace; } } + public string Name { get { return _name; } } + public CSharpType? DeclaringType { get { return _declaringType; } } + public bool IsValueType { get { return _isValueType; } } + public bool IsEnum { get { return _isEnum; } } + public bool IsLiteral => Literal is not null; + public bool IsUnion => _isUnion ??= UnionItemTypes.Count > 0; + public bool IsPublic { get { return _isPublic; } } + public bool IsFrameworkType => _type != null; + public bool IsNullable { get { return _isNullable; } } + public bool IsGenericType => Arguments.Count > 0; + public bool IsCollection => _isCollection ??= TypeIsCollection(); + public Type FrameworkType => _type ?? throw new InvalidOperationException("Not a framework type"); + public Constant? Literal { get; private init; } + internal TypeProvider Implementation => _implementation ?? throw new InvalidOperationException($"Not implemented type: '{Namespace}.{Name}'"); + public IReadOnlyList Arguments { get { return _arguments; } } + public CSharpType InitializationType => _initializationType ??= GetImplementationType(); + public CSharpType PropertyInitializationType => _propertyInitializationType ??= GetPropertyImplementationType(); + public CSharpType ElementType => _elementType ??= GetElementType(); + public CSharpType InputType => _inputType ??= GetInputType(); + public CSharpType OutputType => _outputType ??= GetOutputType(); + public Type? SerializeAs { get; init; } + public IReadOnlyList UnionItemTypes { get { return _unionItemTypes; } private init { _unionItemTypes = value; } } + + private bool TypeIsReadOnlyMemory() + => IsFrameworkType && _type == typeof(ReadOnlyMemory<>); + + private bool TypeIsReadOnlyList() + => IsFrameworkType && (_type == typeof(IEnumerable<>) || _type == typeof(IReadOnlyList<>)); + + private bool TypeIsReadWriteList() + => IsFrameworkType && (_type == typeof(IList<>) || _type == typeof(ICollection<>) || _type == typeof(List<>)); + + private bool TypeIsList() + => IsReadOnlyList || IsReadWriteList || IsReadOnlyMemory; + + private bool TypeIsArray() + => IsFrameworkType && FrameworkType.IsArray; + + private bool TypeIsReadOnlyDictionary() + => IsFrameworkType && _type == typeof(IReadOnlyDictionary<,>); + + private bool TypeIsReadWriteDictionary() + => IsFrameworkType && (_type == typeof(IDictionary<,>)); + + private bool TypeIsDictionary() + => IsReadOnlyDictionary || IsReadWriteDictionary; + + private bool TypeIsCollection() + => IsFrameworkType && (IsDictionary || IsList); + + /// + /// Retrieves the implementation type for the . + /// + /// The implementation type . + private CSharpType GetImplementationType() + { + if (IsFrameworkType) + { + if (IsReadOnlyMemory) + { + return new CSharpType(Arguments[0].FrameworkType.MakeArrayType()); + } + + if (IsList) + { + return new CSharpType(typeof(List<>), Arguments); + } + + if (IsDictionary) + { + return new CSharpType(typeof(Dictionary<,>), Arguments); + } + } + + return this; + } + + /// + /// Retrieves the implementation type for the . + /// + /// The implementation type . + private CSharpType GetPropertyImplementationType() + { + if (IsFrameworkType) + { + if (IsReadOnlyMemory) + { + return new CSharpType(typeof(ReadOnlyMemory<>), Arguments); + } + + if (IsList) + { + return new CSharpType(CodeModelPlugin.Instance.Configuration.ApiTypes.ChangeTrackingListType, Arguments); + } + + if (IsDictionary) + { + return new CSharpType(CodeModelPlugin.Instance.Configuration.ApiTypes.ChangeTrackingDictionaryType, Arguments); + } + } + + return this; + } + + /// + /// Retrieves the element type for the . If the type is not an array, list, or dictionary, an exception is thrown. + /// + /// The element type for the . + /// Thrown when the type is not a framework type, array, list, or dictionary. + private CSharpType GetElementType() + { + if (IsFrameworkType) + { + if (FrameworkType.IsArray) + { + return new CSharpType(FrameworkType.GetElementType()!); + } + + if (IsReadOnlyMemory || IsList) + { + return Arguments[0]; + } + + if (IsDictionary) + { + return Arguments[1]; + } + } + + throw new NotSupportedException(Name); + } + + /// + /// Retrieves the input type for the . + /// + /// The input type. + private CSharpType GetInputType() + { + if (IsFrameworkType) + { + if (IsReadOnlyMemory) + { + return new CSharpType(typeof(ReadOnlyMemory<>), isNullable: IsNullable, arguments: Arguments); + } + + if (IsList) + { + return new CSharpType( + typeof(IEnumerable<>), + isNullable: IsNullable, + arguments: Arguments); + } + } + + return this; + } + + /// + /// Retrieves the output type for the . + /// + /// The output type. + private CSharpType GetOutputType() + { + if (IsFrameworkType) + { + if (IsReadOnlyMemory) + { + return new CSharpType(typeof(ReadOnlyMemory<>), isNullable: IsNullable, arguments: Arguments); + } + + if (IsList) + { + return new CSharpType( + typeof(IReadOnlyList<>), + isNullable: IsNullable, + arguments: Arguments); + } + + if (IsDictionary) + { + return new CSharpType( + typeof(IReadOnlyDictionary<,>), + isNullable: IsNullable, + arguments: Arguments); + } + } + + return this; + } + + private bool TypeIsIEnumerableOfT() => IsFrameworkType && FrameworkType == typeof(IEnumerable<>); + + private bool TypeIsIAsyncEnumerableOfT() => IsFrameworkType && FrameworkType == typeof(IAsyncEnumerable<>); + + /// + /// Types that the provided generic arguments match the type's arguments. + /// + /// The arguments to compare. + /// true if the arguments are equal to the type's arguments. Otherwise, false. + private bool AreArgumentsEqual(IReadOnlyList genericArguments) + { + if (Arguments.Count != genericArguments.Count) + { + return false; + } + + for (int i = 0; i < Arguments.Count; i++) + { + if (!Arguments[i].Equals(genericArguments[i])) + { + return false; + } + } + + return true; + } + + internal bool TryGetCSharpFriendlyName([MaybeNullWhen(false)] out string name) + { + name = _type switch + { + null => null, + var t when t.IsGenericParameter => t.Name, + //if we have an array type and the element is defined in the same assembly then its a generic param array. + var t when t.IsArray && t.Assembly.Equals(GetType().Assembly) => t.Name, + var t when t == typeof(bool) => "bool", + var t when t == typeof(byte) => "byte", + var t when t == typeof(sbyte) => "sbyte", + var t when t == typeof(short) => "short", + var t when t == typeof(ushort) => "ushort", + var t when t == typeof(int) => "int", + var t when t == typeof(uint) => "uint", + var t when t == typeof(long) => "long", + var t when t == typeof(ulong) => "ulong", + var t when t == typeof(char) => "char", + var t when t == typeof(double) => "double", + var t when t == typeof(float) => "float", + var t when t == typeof(object) => "object", + var t when t == typeof(decimal) => "decimal", + var t when t == typeof(string) => "string", + _ => null + }; + + return name != null; + } + + /// + /// Method checks if object of "from" type can be converted to "to" type by calling `ToList` extension method. + /// It returns true if "from" is and "to" is or . + /// + internal static bool RequiresToList(CSharpType from, CSharpType to) + { + if (!to.IsFrameworkType || !from.IsFrameworkType || from.FrameworkType != typeof(IEnumerable<>)) + { + return false; + } + + return to.FrameworkType == typeof(IReadOnlyList<>) || to.FrameworkType == typeof(IList<>); + } + + /// + /// Validates if the current type is equal to . + /// + /// The type to compare. + /// Flag used to control if nullability should be ignored during comparison. + /// true if the types are equal, false otherwise. + protected internal bool Equals(CSharpType other, bool ignoreNullable = false) + => Equals(_implementation, other._implementation) && + _type == other._type && + Arguments.SequenceEqual(other.Arguments) && + (ignoreNullable || IsNullable == other.IsNullable); + + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + return obj is CSharpType csType && Equals(csType, ignoreNullable: false); + } + + public bool Equals(Type type) => + IsFrameworkType && (type.IsGenericType ? type.GetGenericTypeDefinition() == FrameworkType && AreArgumentsEqual(type.GetGenericArguments()) : type == FrameworkType); + + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed override int GetHashCode() + { + // we cache the hashcode since `CSharpType` is meant to be immutable. + if (_hashCode != null) + return _hashCode.Value; + + var hashCode = new HashCode(); + foreach (var arg in Arguments) + { + hashCode.Add(arg); + } + _hashCode = HashCode.Combine(_implementation, _type, hashCode.ToHashCode(), IsNullable); + + return _hashCode.Value; + } + + public CSharpType GetGenericTypeDefinition() + => _type is null + ? throw new NotSupportedException($"{nameof(TypeProvider)} doesn't support generics.") + : new(_type, IsNullable); + + /// + /// Constructs a new with the given nullability . + /// + /// Flag to determine if the new type is nullable. + /// The existing if it is nullable, otherwise a new instance of . + public CSharpType WithNullable(bool isNullable) => + isNullable == IsNullable ? this : IsFrameworkType + ? new CSharpType(FrameworkType, Arguments, isNullable) + : new CSharpType(Implementation, isValueType: IsValueType, isEnum: IsEnum, isNullable: isNullable, arguments: Arguments, declaringType: DeclaringType, ns: Namespace, name: Name); + + public static implicit operator CSharpType(Type type) => new CSharpType(type); + + public sealed override string ToString() + { + return new CodeWriter().Append($"{this}").ToString(); + } + + /// + /// Check whether two CSharpType instances equal or not + /// This is not the same as left.Equals(right) because this function only checks the names. + /// + /// The instance to compare to. + /// true if the instance are equal. false otherwise. + public bool AreNamesEqual(CSharpType? other) + { + if (ReferenceEquals(this, other)) + { + return true; + } + + if (other is null) + { + return false; + } + + if (Namespace != other.Namespace) + return false; + + if (Name != other.Name) + return false; + + if (Arguments.Count != other.Arguments.Count) + return false; + + for (int i = 0; i < Arguments.Count; i++) + { + if (!Arguments[i].AreNamesEqual(other.Arguments[i])) + return false; + } + + return true; + } + + // TO-DO: Implement this once SystemObjectType is implemented: https://github.com/Azure/autorest.csharp/issues/4198 + // internal static CSharpType FromSystemType(Type type, string defaultNamespace, SourceInputModel? sourceInputModel, IEnumerable? backingProperties = null) + // { + // var systemObjectType = SystemObjectType.Create(type, defaultNamespace, sourceInputModel, backingProperties); + // return systemObjectType.Type; + // } + + // internal static CSharpType FromSystemType(BuildContext context, Type type, IEnumerable? backingProperties = null) + // => FromSystemType(type, context.DefaultNamespace, context.SourceInputModel, backingProperties); + + /// + /// This function is used to create a new CSharpType instance with a literal value. + /// If the type is a framework type, the CSharpType will be created with the literal value Constant + /// object. + /// + /// The original type to create a new CSharpType instance from. + /// The literal value of the type, if any. + /// An instance of CSharpType with a literal value property. + public static CSharpType FromLiteral(CSharpType type, object literalValue) + { + if (type.IsFrameworkType) + { + Constant? literal; + try + { + literal = new Constant(literalValue, type); + } + catch + { + literal = null; + } + + return new CSharpType(type.FrameworkType, type.IsNullable) + { + Literal = literal + }; + } + + throw new NotSupportedException("Literals are not supported in non-framework type"); + } + + /// + /// Constructs a CSharpType that represents a union type. + /// + /// The list of union item types. + /// Flag used to determine if a type is nullable. + /// A instance representing those unioned types. + public static CSharpType FromUnion(IReadOnlyList unionItemTypes, bool isNullable) + { + return new CSharpType(typeof(BinaryData), isNullable) + { + UnionItemTypes = unionItemTypes + }; + } + + public CSharpType MakeGenericType(IReadOnlyList arguments) + { + if (IsFrameworkType) + { + return new CSharpType(FrameworkType, arguments, IsNullable); + } + else + { + return new CSharpType(Implementation, isValueType: IsValueType, isEnum: IsEnum, isNullable: IsNullable, arguments: Arguments, declaringType: DeclaringType, ns: Namespace, name: Name); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CodeModelPlugin.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CodeModelPlugin.cs new file mode 100644 index 0000000000..02ed1bc01a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/CodeModelPlugin.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ComponentModel.Composition; +using Microsoft.Generator.CSharp.Expressions; +using Microsoft.Generator.CSharp.Input; +using Microsoft.Generator.CSharp.Writers; + +namespace Microsoft.Generator.CSharp +{ + /// + /// Base class for code model plugins. This class is exported via MEF and can be implemented by an inherited plugin class. + /// + [InheritedExport] + public abstract class CodeModelPlugin + { + private static CodeModelPlugin? _instance; + internal static CodeModelPlugin Instance => _instance ?? throw new InvalidOperationException("CodeModelPlugin is not initialized"); + + public Configuration Configuration { get; } + + [ImportingConstructor] + public CodeModelPlugin(GeneratorContext context) + { + _instance = this; + Configuration = context.Configuration; + } + + // Extensibility points to be implemented by a plugin + public abstract ApiTypes ApiTypes { get; } + public abstract CodeWriterExtensionMethods CodeWriterExtensionMethods { get; } + public abstract TypeFactory TypeFactory { get; } + public abstract ExtensibleSnippets ExtensibleSnippets { get; } + public abstract OutputLibrary GetOutputLibrary(InputNamespace input); + public virtual ExpressionTypeProviderWriter GetExpressionTypeProviderWriter(CodeWriter writer, ModelTypeProvider model) => new ExpressionTypeProviderWriter(writer, model); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Configuration.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Configuration.cs new file mode 100644 index 0000000000..d4758553f3 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Configuration.cs @@ -0,0 +1,220 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; + +namespace Microsoft.Generator.CSharp +{ + /// + /// Contains configuration for the generator implemented as a singleton. + /// + public class Configuration + { + private const string ConfigurationFileName = "Configuration.json"; + + // TO-DO: decouple and refactor apitypes from configuration https://github.com/Azure/autorest.csharp/issues/4226 +#pragma warning disable CS0649 // Field 'Configuration._apiTypes' & 'Configuration.__extensibleSnippets' is never assigned to, and will always have its default value null + private ApiTypes? _apiTypes; +#pragma warning restore CS0649 // Field 'Configuration._apiTypes' & 'Configuration.__extensibleSnippets' is never assigned to, and will always have its default value null + + private Configuration( + string outputPath, + Dictionary additionalConfigOptions, + bool clearOutputFolder, + bool generateModelFactory, + bool generateSampleProject, + bool generateTestProject, + string libraryName, + bool useModelNamespace, + string libraryNamespace) + { + OutputDirectory = outputPath; + AdditionalConfigOptions = additionalConfigOptions; + ClearOutputFolder = clearOutputFolder; + GenerateModelFactory = generateModelFactory; + GenerateSampleProject = generateSampleProject; + GenerateTestProject = generateTestProject; + LibraryName = libraryName; + UseModelNamespace = useModelNamespace; + Namespace = libraryNamespace; + } + + /// + /// Contains the known set of configuration options. + /// + private static class Options + { + public const string ClearOutputFolder = "clear-output-folder"; + public const string GenerateModelFactory = "generate-model-factory"; + public const string GenerateSampleProject = "generate-sample-project"; + public const string GenerateTestProject = "generate-test-project"; + public const string LibraryName = "library-name"; + public const string Namespace = "namespace"; + public const string UseModelNamespace = "use-model-namespace"; + } + + public ApiTypes ApiTypes => _apiTypes ?? throw new InvalidOperationException("Configuration has not been initialized"); + + /// Returns the singleton instance of the configuration. + public string Namespace { get; } + + internal string OutputDirectory { get; } + + private string? _projectDirectory; + internal string ProjectDirectory => _projectDirectory ??= Path.Combine(OutputDirectory, "src"); + + internal string LibraryName { get; } + + /// + /// True if the output folder should be cleared before generating the code. + /// + internal bool ClearOutputFolder { get; private set; } + + /// + /// Whether we will generate model factory for this library. + /// If true (default), the model factory will be generated. If false, the model factory will not be generated. + /// + internal bool GenerateModelFactory { get; private set; } + + /// + /// True if a sample project should be generated. + /// + internal bool GenerateSampleProject { get; private set; } + + /// + /// True if a test project should be generated. + /// + internal bool GenerateTestProject { get; private set; } + + // The additional configuration options read from the input configuration file. + public Dictionary AdditionalConfigOptions { get; } + + /// + /// True if the models contain a separate namespace. + /// + internal bool UseModelNamespace { get; private set; } + + /// + /// Initializes the configuration from the given path to the configuration file. + /// + /// The path to the configuration JSON file. + internal static Configuration Load(string outputPath, string? json = null) + { + var configFile = Path.Combine(outputPath, ConfigurationFileName); + if (!File.Exists(configFile) && json is null) + { + throw new InvalidOperationException($"Configuration file {outputPath} does not exist."); + } + + var root = json is null + ? JsonDocument.Parse(File.Open(configFile, FileMode.Open, FileAccess.Read, FileShare.Read)).RootElement + : JsonDocument.Parse(json).RootElement; + + return new Configuration( + outputPath.Equals(string.Empty) ? outputPath :Path.GetFullPath(outputPath), + ParseAdditionalConfigOptions(root), + ReadOption(root, Options.ClearOutputFolder), + ReadOption(root, Options.GenerateModelFactory), + ReadOption(root, Options.GenerateSampleProject), + ReadOption(root, Options.GenerateTestProject), + ReadRequiredStringOption(root, Options.LibraryName), + ReadOption(root, Options.UseModelNamespace), + ReadRequiredStringOption(root, Options.Namespace)); + } + + /// + /// The default values for the boolean configuration options. + /// + private static readonly Dictionary _defaultBoolOptionValues = new() + { + { Options.UseModelNamespace, true }, + { Options.GenerateModelFactory, true }, + { Options.GenerateSampleProject, true }, + { Options.ClearOutputFolder, false }, + { Options.GenerateTestProject, false } + }; + + /// + /// The known set of configuration options. + /// + private static readonly HashSet _knownOptions = new() + { + Options.ClearOutputFolder, + Options.GenerateModelFactory, + Options.GenerateSampleProject, + Options.GenerateTestProject, + Options.LibraryName, + Options.UseModelNamespace, + Options.Namespace, + }; + + private static bool ReadOption(JsonElement root, string option) + { + if (root.TryGetProperty(option, out JsonElement value)) + { + return value.GetBoolean(); + } + else + { + return GetDefaultBoolOptionValue(option); + } + } + + private static string ReadRequiredStringOption(JsonElement root, string option) + { + return ReadStringOption(root, option) ?? throw new InvalidOperationException($"Unable to parse required option {option} from configuration."); + } + + + private static string? ReadStringOption(JsonElement root, string option) + { + if (root.TryGetProperty(option, out JsonElement value)) + return value.GetString(); + + return null; + } + + /// + /// Returns the default value for the given option. + /// + /// The option to parse. + private static bool GetDefaultBoolOptionValue(string option) + { + return _defaultBoolOptionValues.TryGetValue(option, out bool defaultValue) && defaultValue; + } + + /// + /// Parses the additional configuration options from the given JSON element root and stores them in a dictionary. + /// + /// The json root element to parse. + /// A dictionary containing the set of configuration options represented as key-value pairs. + private static Dictionary ParseAdditionalConfigOptions(JsonElement root) + { + var optionsDict = new Dictionary(); + foreach (var property in root.EnumerateObject()) + { + var propertyName = property.Name; + if (!IsKnownOption(propertyName)) + { + BinaryData value = BinaryData.FromObjectAsJson(property.Value); + optionsDict.TryAdd(propertyName, value); + } + } + + return optionsDict; + } + + /// + /// Validates if the given option is a known configuration option from ."/>. + /// + /// The configuration option name. + /// true if the option is a known option. + private static bool IsKnownOption(string option) + { + return _knownOptions.Contains(option); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Constants.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Constants.cs new file mode 100644 index 0000000000..795ae10192 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Constants.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp +{ + /// + /// Constants shared between the components of the core generator. + /// + internal static class Constants + { + /// + /// The default name of the generated code folder that the generator will output to. + /// + public const string DefaultGeneratedCodeFolderName = "Generated"; + + /// + /// The default name of the generated test folder that the generator will output to. + /// + public const string DefaultGeneratedTestFolderName = "GeneratedTests"; + + /// + /// The default name of the generated code folder in the project workspace.. + /// + public const string DefaultGeneratedCodeProjectFolderName = "GeneratedCode"; + + /// + /// The default name of the configuration file. + /// + public const string DefaultConfigurationFileName = "Configuration.json"; + + /// + /// The default name of the code model file. + /// + public const string DefaultCodeModelFileName = "tspCodeModel.json"; + + /// + /// The error message when the configuration has not been initialized. + /// + public const string ConfigurationNotInitializedError = "Configuration has not been initialized."; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/DiagnosticScope.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/DiagnosticScope.cs new file mode 100644 index 0000000000..7d93630501 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/DiagnosticScope.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp +{ + public class DiagnosticScope : IDisposable + { + private readonly CodeWriter.CodeWriterScope _scope; + private readonly CodeWriterDeclaration _scopeVariable; + private readonly CodeWriter _writer; + + public DiagnosticScope(CodeWriter.CodeWriterScope scope, CodeWriterDeclaration scopeVariable, CodeWriter writer) + { + _scope = scope; + _scopeVariable = scopeVariable; + _writer = writer; + } + + public void Dispose() + { + _scope.Dispose(); + using (_writer.Scope($"catch ({typeof(Exception)} e)")) + { + _writer.WriteLine($"{_scopeVariable}.Failed(e);"); + _writer.WriteLine($"throw;"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/CodeBlocks/DiagnosticScopeMethodBodyBlock.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/CodeBlocks/DiagnosticScopeMethodBodyBlock.cs new file mode 100644 index 0000000000..15425c72af --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/CodeBlocks/DiagnosticScopeMethodBodyBlock.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record DiagnosticScopeMethodBodyBlock(Diagnostic Diagnostic, Reference ClientDiagnosticsReference, MethodBodyStatement InnerStatement) : MethodBodyStatement; +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/CodeBlocks/ParameterValidationBlock.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/CodeBlocks/ParameterValidationBlock.cs new file mode 100644 index 0000000000..061141630a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/CodeBlocks/ParameterValidationBlock.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record ParameterValidationBlock(IReadOnlyList Parameters, bool IsLegacy = false) : MethodBodyStatement + { + public sealed override void Write(CodeWriter writer) + { + if (IsLegacy) + { + writer.WriteParameterNullChecks(Parameters); + } + else + { + writer.WriteParametersValidation(Parameters); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.JsonElementSnippets.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.JsonElementSnippets.cs new file mode 100644 index 0000000000..9930629e80 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.JsonElementSnippets.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public abstract partial class ExtensibleSnippets + { + public abstract class JsonElementSnippets + { + public abstract ValueExpression GetBytesFromBase64(JsonElementExpression element, string? format); + public abstract ValueExpression GetChar(JsonElementExpression element); + public abstract ValueExpression GetDateTimeOffset(JsonElementExpression element, string? format); + public abstract ValueExpression GetObject(JsonElementExpression element); + public abstract ValueExpression GetTimeSpan(JsonElementExpression element, string? format); + + public abstract MethodBodyStatement ThrowNonNullablePropertyIsNull(JsonPropertyExpression property); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.ModelSnippets.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.ModelSnippets.cs new file mode 100644 index 0000000000..c2f685b840 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.ModelSnippets.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public abstract partial class ExtensibleSnippets + { + public abstract class ModelSnippets + { + public abstract Method BuildConversionToRequestBodyMethod(MethodSignatureModifiers modifiers); + public abstract Method BuildFromOperationResponseMethod(TypeProvider typeProvider, MethodSignatureModifiers modifiers); + public abstract TypedValueExpression InvokeToRequestBodyMethod(TypedValueExpression model); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.RestOperationsSnippets.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.RestOperationsSnippets.cs new file mode 100644 index 0000000000..1f3efc2a16 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.RestOperationsSnippets.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public abstract partial class ExtensibleSnippets + { + public abstract class RestOperationsSnippets + { + public abstract TypedValueExpression InvokeServiceOperationCall(TypedValueExpression pipeline, TypedValueExpression message, bool async); + + public abstract TypedValueExpression GetTypedResponseFromValue(TypedValueExpression value, TypedValueExpression response); + public abstract TypedValueExpression GetTypedResponseFromModel(TypeProvider typeProvider, TypedValueExpression response); + public abstract TypedValueExpression GetTypedResponseFromEnum(EnumType enumType, TypedValueExpression response); + public abstract TypedValueExpression GetTypedResponseFromBinaryData(Type responseType, TypedValueExpression response, string? contentType = null); + + public abstract MethodBodyStatement DeclareHttpMessage(MethodSignatureBase createRequestMethodSignature, out TypedValueExpression message); + public abstract MethodBodyStatement DeclareContentWithUtf8JsonWriter(out TypedValueExpression content, out Utf8JsonWriterExpression writer); + public abstract MethodBodyStatement DeclareContentWithXmlWriter(out TypedValueExpression content, out XmlWriterExpression writer); + public abstract MethodBodyStatement InvokeServiceOperationCallAndReturnHeadAsBool(TypedValueExpression pipeline, TypedValueExpression message, TypedValueExpression clientDiagnostics, bool async); + public abstract StreamExpression GetContentStream(TypedValueExpression response); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.XElementSnippets.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.XElementSnippets.cs new file mode 100644 index 0000000000..9e0bb649d1 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.XElementSnippets.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public abstract partial class ExtensibleSnippets + { + public abstract class XElementSnippets + { + public abstract ValueExpression GetBytesFromBase64Value(XElementExpression xElement, string? format); + public abstract ValueExpression GetDateTimeOffsetValue(XElementExpression xElement, string? format); + public abstract ValueExpression GetObjectValue(XElementExpression xElement, string? format); + public abstract ValueExpression GetTimeSpanValue(XElementExpression xElement, string? format); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.XmlWriterSnippets.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.XmlWriterSnippets.cs new file mode 100644 index 0000000000..b5e097f9e8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.XmlWriterSnippets.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public abstract partial class ExtensibleSnippets + { + public abstract class XmlWriterSnippets + { + public abstract MethodBodyStatement WriteValue(XmlWriterExpression xmlWriter, ValueExpression value, string format); + public abstract MethodBodyStatement WriteObjectValue(XmlWriterExpression xmlWriter, ValueExpression value, string? nameHint); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.cs new file mode 100644 index 0000000000..b4137e484b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ExtensibleSnippets.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public abstract partial class ExtensibleSnippets + { + public abstract JsonElementSnippets JsonElement { get; } + public abstract ModelSnippets Model { get; } + public abstract RestOperationsSnippets RestOperations { get; } + public abstract XElementSnippets XElement { get; } + public abstract XmlWriterSnippets XmlWriter { get; } + + protected static InvokeStaticMethodExpression InvokeExtension(CSharpType extensionType, ValueExpression instance, string methodName) + => new(extensionType, methodName, new[] { instance }, CallAsAsync: false, CallAsExtension: true); + + protected static InvokeStaticMethodExpression InvokeExtension(CSharpType extensionType, ValueExpression instance, string methodName, ValueExpression arg) + => new(extensionType, methodName, new[] { instance, arg }, CallAsAsync: false, CallAsExtension: true); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.Argument.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.Argument.cs new file mode 100644 index 0000000000..ead6c30dd1 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.Argument.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public static partial class Snippets + { + public class Argument + { + public static MethodBodyStatement AssertNotNull(ValueExpression variable) + { + return new IfStatement(Equal(variable, Null)) + { + new ThrowStatement(ThrowExpression(New.ArgumentNullException(variable))) + }; + } + + public static MethodBodyStatement AssertNotNullOrEmpty(ValueExpression variable) + { + return new List() + { + AssertNotNull(variable), + new IfStatement(Equal(new MemberExpression(variable, "Length"), Literal(0))) + { + new ThrowStatement(ThrowExpression(New.ArgumentException(variable, string.Empty))) + } + }; + } + + public static MethodBodyStatement ValidateParameter(Parameter parameter) + { + return parameter.Validation switch + { + ValidationType.AssertNotNullOrEmpty => AssertNotNullOrEmpty(parameter), + ValidationType.AssertNotNull => AssertNotNull(parameter), + _ => EmptyStatement + }; + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.DeclarationStatements.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.DeclarationStatements.cs new file mode 100644 index 0000000000..a30c29fb5e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.DeclarationStatements.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public static partial class Snippets + { + public static MethodBodyStatement UsingDeclare(string name, CSharpType type, ValueExpression value, out VariableReference variable) + { + var declaration = new CodeWriterDeclaration(name); + variable = new VariableReference(type, declaration); + return new UsingDeclareVariableStatement(type, declaration, value); + } + + public static MethodBodyStatement UsingDeclare(string name, JsonDocumentExpression value, out JsonDocumentExpression variable) + => UsingDeclare(name, value, d => new JsonDocumentExpression(d), out variable); + + public static MethodBodyStatement UsingDeclare(string name, StreamExpression value, out StreamExpression variable) + => UsingDeclare(name, value, d => new StreamExpression(d), out variable); + + public static MethodBodyStatement UsingDeclare(VariableReference variable, ValueExpression value) + => new UsingDeclareVariableStatement(variable.Type, variable.Declaration, value); + + public static MethodBodyStatement Declare(CSharpType variableType, string name, ValueExpression value, out TypedValueExpression variable) + { + var variableRef = new VariableReference(variableType, name); + variable = variableRef; + return Declare(variableRef, value); + } + + public static MethodBodyStatement Declare(string name, BinaryDataExpression value, out BinaryDataExpression variable) + => Declare(name, value, d => new BinaryDataExpression(d), out variable); + + public static MethodBodyStatement Declare(string name, DictionaryExpression value, out DictionaryExpression variable) + => Declare(name, value, d => new DictionaryExpression(value.KeyType, value.ValueType, d), out variable); + + public static MethodBodyStatement Declare(string name, EnumerableExpression value, out EnumerableExpression variable) + => Declare(name, value, d => new EnumerableExpression(value.ItemType, d), out variable); + + public static MethodBodyStatement Declare(string name, JsonElementExpression value, out JsonElementExpression variable) + => Declare(name, value, d => new JsonElementExpression(d), out variable); + + public static MethodBodyStatement Declare(string name, ListExpression value, out ListExpression variable) + => Declare(name, value, d => new ListExpression(value.ItemType, d), out variable); + + public static MethodBodyStatement Declare(string name, StreamReaderExpression value, out StreamReaderExpression variable) + => Declare(name, value, d => new StreamReaderExpression(d), out variable); + + public static MethodBodyStatement Declare(string name, TypedValueExpression value, out TypedValueExpression variable) + { + var declaration = new VariableReference(value.Type, name); + variable = declaration; + return Declare(declaration, value); + } + + public static MethodBodyStatement Declare(VariableReference variable, ValueExpression value) + => new DeclareVariableStatement(variable.Type, variable.Declaration, value); + + public static MethodBodyStatement UsingVar(string name, JsonDocumentExpression value, out JsonDocumentExpression variable) + => UsingVar(name, value, d => new JsonDocumentExpression(d), out variable); + + public static MethodBodyStatement Var(string name, DictionaryExpression value, out DictionaryExpression variable) + => Var(name, value, d => new DictionaryExpression(value.KeyType, value.ValueType, d), out variable); + + public static MethodBodyStatement Var(string name, ListExpression value, out ListExpression variable) + => Var(name, value, d => new ListExpression(value.ItemType, d), out variable); + + public static MethodBodyStatement Var(string name, StringExpression value, out StringExpression variable) + => Var(name, value, d => new StringExpression(d), out variable); + + public static MethodBodyStatement Var(string name, Utf8JsonWriterExpression value, out Utf8JsonWriterExpression variable) + => Var(name, value, d => new Utf8JsonWriterExpression(d), out variable); + + public static MethodBodyStatement Var(string name, XDocumentExpression value, out XDocumentExpression variable) + => Var(name, value, d => new XDocumentExpression(d), out variable); + + public static MethodBodyStatement Var(string name, TypedValueExpression value, out TypedValueExpression variable) + { + var reference = new VariableReference(value.Type, name); + variable = reference; + return Var(reference, value); + } + + public static MethodBodyStatement Var(VariableReference variable, ValueExpression value) + => new DeclareVariableStatement(null, variable.Declaration, value); + + private static MethodBodyStatement UsingDeclare(string name, T value, Func factory, out T variable) where T : TypedValueExpression + { + var declaration = new CodeWriterDeclaration(name); + variable = factory(new VariableReference(value.Type, declaration)); + return new UsingDeclareVariableStatement(value.Type, declaration, value); + } + + private static MethodBodyStatement UsingVar(string name, T value, Func factory, out T variable) where T : TypedValueExpression + { + var declaration = new CodeWriterDeclaration(name); + variable = factory(new VariableReference(value.Type, declaration)); + return new UsingDeclareVariableStatement(null, declaration, value); + } + + private static MethodBodyStatement Declare(string name, T value, Func factory, out T variable) where T : TypedValueExpression + { + var declaration = new CodeWriterDeclaration(name); + variable = factory(new VariableReference(value.Type, declaration)); + return new DeclareVariableStatement(value.Type, declaration, value); + } + + private static MethodBodyStatement Var(string name, T value, Func factory, out T variable) where T : TypedValueExpression + { + var declaration = new CodeWriterDeclaration(name); + variable = factory(new VariableReference(value.Type, declaration)); + return new DeclareVariableStatement(null, declaration, value); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.Linq.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.Linq.cs new file mode 100644 index 0000000000..4b73523b8a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.Linq.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public static partial class Snippets + { + public static class Linq + { + public static ValueExpression ToList(ValueExpression expression) + { + return new InvokeStaticMethodExpression(typeof(Enumerable), nameof(Enumerable.ToList), new[] { expression }, CallAsExtension: true); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.New.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.New.cs new file mode 100644 index 0000000000..38d11a2005 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.New.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public static partial class Snippets + { + public static class New + { + public static ValueExpression ArgumentOutOfRangeException(EnumType enumType, Parameter valueParameter) + => Instance(typeof(ArgumentOutOfRangeException), Nameof(valueParameter), valueParameter, Literal($"Unknown {enumType.Name} value.")); + public static ValueExpression ArgumentOutOfRangeException(ValueExpression valueParameter, string message, bool wrapInNameOf = true) + => Instance(typeof(ArgumentOutOfRangeException), wrapInNameOf ? Nameof(valueParameter) : valueParameter, Literal(message)); + + public static ValueExpression NotImplementedException(ValueExpression message) + => Instance(typeof(NotImplementedException), message); + + public static ValueExpression NotSupportedException(ValueExpression message) + => Instance(typeof(NotSupportedException), message); + + public static ValueExpression InvalidOperationException(ValueExpression message) + => Instance(typeof(InvalidOperationException), message); + + public static ValueExpression ArgumentNullException(ValueExpression parameter, bool wrapInNameOf = true) + => Instance(typeof(ArgumentNullException), wrapInNameOf ? Nameof(parameter) : parameter); + + public static ValueExpression ArgumentException(ValueExpression parameter, string message, bool wrapInNameOf = true) + => ArgumentException(parameter, Literal(message), wrapInNameOf); + + public static ValueExpression ArgumentException(ValueExpression parameter, ValueExpression message, bool wrapInNameOf = true) + => Instance(typeof(ArgumentException), message, wrapInNameOf ? Nameof(parameter) : parameter); + + public static ValueExpression JsonException(ValueExpression message) + => Instance(typeof(JsonException), message); + + public static EnumerableExpression Array(CSharpType? elementType) => new(elementType ?? typeof(object), new NewArrayExpression(elementType)); + public static EnumerableExpression Array(CSharpType? elementType, params ValueExpression[] items) => new(elementType ?? typeof(object), new NewArrayExpression(elementType, new ArrayInitializerExpression(items))); + public static EnumerableExpression Array(CSharpType? elementType, bool isInline, params ValueExpression[] items) => new(elementType ?? typeof(object), new NewArrayExpression(elementType, new ArrayInitializerExpression(items, isInline))); + public static EnumerableExpression Array(CSharpType? elementType, ValueExpression size) => new(elementType ?? typeof(object), new NewArrayExpression(elementType, Size: size)); + + public static DictionaryExpression Dictionary(CSharpType keyType, CSharpType valueType) + => new(keyType, valueType, new NewDictionaryExpression(new CSharpType(typeof(Dictionary<,>), keyType, valueType))); + public static DictionaryExpression Dictionary(CSharpType keyType, CSharpType valueType, params (ValueExpression Key, ValueExpression Value)[] values) + => new(keyType, valueType, new NewDictionaryExpression(new CSharpType(typeof(Dictionary<,>), keyType, valueType), new DictionaryInitializerExpression(values))); + + public static TypedValueExpression JsonSerializerOptions() => new FrameworkTypeExpression(typeof(JsonSerializerOptions), new NewJsonSerializerOptionsExpression()); + + public static ListExpression List(CSharpType elementType) => new(elementType, Instance(new CSharpType(typeof(List<>), elementType))); + + public static StreamReaderExpression StreamReader(ValueExpression stream) => new(Instance(typeof(StreamReader), stream)); + + public static TimeSpanExpression TimeSpan(int hours, int minutes, int seconds) => new(Instance(typeof(TimeSpan), Int(hours), Int(minutes), Int(seconds))); + public static TypedValueExpression Uri(string uri) => Instance(typeof(Uri), Literal(uri)); + + public static ValueExpression Anonymous(string key, ValueExpression value) => Anonymous(new Dictionary { [key] = value }); + public static ValueExpression Anonymous(IReadOnlyDictionary? properties) => new KeywordExpression("new", new ObjectInitializerExpression(properties, UseSingleLine: false)); + public static ValueExpression Instance(ConstructorSignature ctorSignature, IReadOnlyList arguments, IReadOnlyDictionary? properties = null) => new NewInstanceExpression(ctorSignature.Type, arguments, properties != null ? new ObjectInitializerExpression(properties) : null); + public static ValueExpression Instance(ConstructorSignature ctorSignature, IReadOnlyDictionary? properties = null) => Instance(ctorSignature, ctorSignature.Parameters.Select(p => (ValueExpression)p).ToArray(), properties); + public static ValueExpression Instance(CSharpType type, IReadOnlyList arguments) => new NewInstanceExpression(type, arguments); + public static ValueExpression Instance(CSharpType type, params ValueExpression[] arguments) => new NewInstanceExpression(type, arguments); + public static ValueExpression Instance(CSharpType type, IReadOnlyDictionary properties) => new NewInstanceExpression(type, System.Array.Empty(), new ObjectInitializerExpression(properties)); + public static TypedValueExpression Instance(Type type, params ValueExpression[] arguments) => new FrameworkTypeExpression(type, new NewInstanceExpression(type, arguments)); + public static TypedValueExpression Instance(Type type, IReadOnlyDictionary properties) => new FrameworkTypeExpression(type, new NewInstanceExpression(type, System.Array.Empty(), new ObjectInitializerExpression(properties))); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.cs new file mode 100644 index 0000000000..58d9998142 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Snippets.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public static partial class Snippets + { + public static ExtensibleSnippets Extensible => CodeModelPlugin.Instance.ExtensibleSnippets; + public static MethodBodyStatement EmptyStatement { get; } = new(); + public static MethodBodyStatement AsStatement(this IEnumerable statements) => statements.ToArray(); + + public static ValueExpression Dash { get; } = new KeywordExpression("_", null); + public static ValueExpression Default { get; } = new KeywordExpression("default", null); + public static ValueExpression Null { get; } = new KeywordExpression("null", null); + public static ValueExpression This { get; } = new KeywordExpression("this", null); + public static BoolExpression True { get; } = new(new KeywordExpression("true", null)); + public static BoolExpression False { get; } = new(new KeywordExpression("false", null)); + + public static BoolExpression Bool(bool value) => value ? True : False; + public static IntExpression Int(int value) => new IntExpression(Literal(value)); + public static LongExpression Long(long value) => new LongExpression(Literal(value)); + public static ValueExpression Float(float value) => new FormattableStringToExpression($"{value}f"); + public static ValueExpression Double(double value) => new FormattableStringToExpression($"{value}d"); + + public static ValueExpression Nameof(ValueExpression expression) => new InvokeInstanceMethodExpression(null, "nameof", new[] { expression }, null, false); + public static ValueExpression ThrowExpression(ValueExpression expression) => new KeywordExpression("throw", expression); + + public static ValueExpression NullCoalescing(ValueExpression left, ValueExpression right) => new BinaryOperatorExpression("??", left, right); + // TO-DO: Migrate remaining class as part of output classes migration : https://github.com/Azure/autorest.csharp/issues/4198 + //public static ValueExpression EnumValue(EnumType type, EnumTypeValue value) => new MemberExpression(new TypeReference(type.Type), value.Declaration.Name); + public static ValueExpression FrameworkEnumValue(TEnum value) where TEnum : struct, Enum => new MemberExpression(new TypeReference(typeof(TEnum)), Enum.GetName(value)!); + + public static ValueExpression RemoveAllNullConditional(ValueExpression expression) + => expression switch + { + NullConditionalExpression nullConditional => RemoveAllNullConditional(nullConditional.Inner), + MemberExpression { Inner: { } inner } member => member with { Inner = RemoveAllNullConditional(inner) }, + TypedValueExpression typed => typed with { Untyped = RemoveAllNullConditional(typed.Untyped) }, + _ => expression + }; + + public static TypedValueExpression RemoveAllNullConditional(TypedValueExpression expression) + => expression with { Untyped = RemoveAllNullConditional(expression.Untyped) }; + + public static ValueExpression Literal(object? value) => new FormattableStringToExpression($"{value:L}"); + + public static StringExpression Literal(string? value) => new(value is null ? Null : new StringLiteralExpression(value, false)); + public static StringExpression LiteralU8(string value) => new(new StringLiteralExpression(value, true)); + + public static BoolExpression GreaterThan(ValueExpression left, ValueExpression right) => new(new BinaryOperatorExpression(">", left, right)); + public static BoolExpression LessThan(ValueExpression left, ValueExpression right) => new(new BinaryOperatorExpression("<", left, right)); + public static BoolExpression Equal(ValueExpression left, ValueExpression right) => new(new BinaryOperatorExpression("==", left, right)); + public static BoolExpression NotEqual(ValueExpression left, ValueExpression right) => new(new BinaryOperatorExpression("!=", left, right)); + + public static BoolExpression Is(XElementExpression value, string name, out XElementExpression xElement) + => Is(value, name, d => new XElementExpression(d), out xElement); + public static BoolExpression Is(XAttributeExpression value, string name, out XAttributeExpression xAttribute) + => Is(value, name, d => new XAttributeExpression(d), out xAttribute); + public static BoolExpression Is(ValueExpression left, ValueExpression right) + => new(new BinaryOperatorExpression("is", left, right)); + + public static BoolExpression Or(BoolExpression left, BoolExpression right) => new(new BinaryOperatorExpression("||", left.Untyped, right.Untyped)); + public static BoolExpression And(BoolExpression left, BoolExpression right) => new(new BinaryOperatorExpression("&&", left.Untyped, right.Untyped)); + public static BoolExpression Not(BoolExpression operand) => new(new UnaryOperatorExpression("!", operand, false)); + + public static MethodBodyStatement EmptyLine => new EmptyLineStatement(); + public static KeywordStatement Continue => new("continue", null); + public static KeywordStatement Break => new("break", null); + public static KeywordStatement Return(ValueExpression expression) => new("return", expression); + public static KeywordStatement Return() => new("return", null); + public static KeywordStatement Throw(ValueExpression expression) => new("throw", expression); + + public static EnumerableExpression InvokeArrayEmpty(CSharpType arrayItemType) + => new(arrayItemType, new InvokeStaticMethodExpression(typeof(Array), nameof(Array.Empty), Array.Empty(), new[] { arrayItemType })); + + public static StreamExpression InvokeFileOpenRead(string filePath) + => new(new InvokeStaticMethodExpression(typeof(System.IO.File), nameof(System.IO.File.OpenRead), new[] { Literal(filePath) })); + public static StreamExpression InvokeFileOpenWrite(string filePath) + => new(new InvokeStaticMethodExpression(typeof(System.IO.File), nameof(System.IO.File.OpenWrite), new[] { Literal(filePath) })); + + public static MethodBodyStatement InvokeCustomSerializationMethod(string methodName, Utf8JsonWriterExpression utf8JsonWriter) + => new InvokeInstanceMethodStatement(null, methodName, utf8JsonWriter); + + public static MethodBodyStatement InvokeCustomBicepSerializationMethod(string methodName, StringBuilderExpression stringBuilder) + => new InvokeInstanceMethodStatement(null, methodName, stringBuilder); + + public static MethodBodyStatement InvokeCustomDeserializationMethod(string methodName, JsonPropertyExpression jsonProperty, CodeWriterDeclaration variable) + => new InvokeStaticMethodStatement(null, methodName, new ValueExpression[] { jsonProperty, new FormattableStringToExpression($"ref {variable}") }); + + public static AssignValueIfNullStatement AssignIfNull(ValueExpression variable, ValueExpression expression) => new(variable, expression); + public static AssignValueStatement Assign(ValueExpression variable, ValueExpression expression) => new(variable, expression); + + public static MethodBodyStatement AssignOrReturn(ValueExpression? variable, ValueExpression expression) + => variable != null ? Assign(variable, expression) : Return(expression); + + public static MethodBodyStatement InvokeConsoleWriteLine(ValueExpression expression) + => new InvokeStaticMethodStatement(typeof(Console), nameof(Console.WriteLine), expression); + + private static BoolExpression Is(T value, string name, Func factory, out T variable) where T : TypedValueExpression + { + var declaration = new CodeWriterDeclaration(name); + variable = factory(new VariableReference(value.Type, declaration)); + return new(new BinaryOperatorExpression("is", value, new FormattableStringToExpression($"{value.Type} {declaration:D}"))); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/AssignValueIfNullStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/AssignValueIfNullStatement.cs new file mode 100644 index 0000000000..567a40e13a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/AssignValueIfNullStatement.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record AssignValueIfNullStatement(ValueExpression To, ValueExpression From) : MethodBodyStatement + { + public override void Write(CodeWriter writer) + { + To.Write(writer); + writer.AppendRaw(" ??= "); + From.Write(writer); + writer.WriteRawLine(";"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/AssignValueStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/AssignValueStatement.cs new file mode 100644 index 0000000000..934e3c7611 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/AssignValueStatement.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record AssignValueStatement(ValueExpression To, ValueExpression From) : MethodBodyStatement + { + public override void Write(CodeWriter writer) + { + To.Write(writer); + writer.AppendRaw(" = "); + From.Write(writer); + writer.WriteRawLine(";"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/CatchStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/CatchStatement.cs new file mode 100644 index 0000000000..a58b3a1276 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/CatchStatement.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record CatchStatement(ValueExpression? Exception, MethodBodyStatement Body) + { + public void Write(CodeWriter writer) + { + writer.AppendRaw("catch"); + if (Exception != null) + { + writer.AppendRaw(" ("); + Exception.Write(writer); + writer.AppendRaw(")"); + } + writer.WriteRawLine("{"); + Body.Write(writer); + writer.WriteRawLine("}"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/DeclareLocalFunctionStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/DeclareLocalFunctionStatement.cs new file mode 100644 index 0000000000..4b91d3337e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/DeclareLocalFunctionStatement.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record DeclareLocalFunctionStatement(CodeWriterDeclaration Name, IReadOnlyList Parameters, CSharpType ReturnType, ValueExpression? BodyExpression, MethodBodyStatement? BodyStatement) : MethodBodyStatement + { + internal DeclareLocalFunctionStatement(CodeWriterDeclaration Name, IReadOnlyList Parameters, CSharpType ReturnType, MethodBodyStatement BodyStatement) + : this(Name, Parameters, ReturnType, null, BodyStatement) { } + + internal DeclareLocalFunctionStatement(CodeWriterDeclaration Name, IReadOnlyList Parameters, CSharpType ReturnType, ValueExpression BodyExpression) + : this(Name, Parameters, ReturnType, BodyExpression, null) { } + + public override void Write(CodeWriter writer) + { + writer.Append($"{ReturnType} {Name:D}("); + foreach (var parameter in Parameters) + { + writer.Append($"{parameter.Type} {parameter.Name}, "); + } + + writer.RemoveTrailingComma(); + writer.AppendRaw(")"); + if (BodyExpression is not null) + { + writer.AppendRaw(" => "); + BodyExpression.Write(writer); + writer.WriteRawLine(";"); + } + else if (BodyStatement is not null) + { + using (writer.Scope()) + { + BodyStatement.Write(writer); + } + } + else + { + throw new InvalidOperationException($"{nameof(DeclareLocalFunctionStatement)}.{nameof(BodyExpression)} and {nameof(DeclareLocalFunctionStatement)}.{nameof(BodyStatement)} can't both be null."); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/DeclareVariableStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/DeclareVariableStatement.cs new file mode 100644 index 0000000000..b52288741d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/DeclareVariableStatement.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record DeclareVariableStatement(CSharpType? Type, CodeWriterDeclaration Name, ValueExpression Value) : MethodBodyStatement + { + public override void Write(CodeWriter writer) + { + if (Type != null) + { + writer.Append($"{Type}"); + } + else + { + writer.AppendRaw("var"); + } + + writer.Append($" {Name:D} = "); + Value.Write(writer); + writer.WriteRawLine(";"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/EmptyLineStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/EmptyLineStatement.cs new file mode 100644 index 0000000000..ef2f7f806e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/EmptyLineStatement.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record EmptyLineStatement() : MethodBodyStatement + { + public override void Write(CodeWriter writer) + { + writer.WriteLine(); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/ForStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/ForStatement.cs new file mode 100644 index 0000000000..f4ec511630 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/ForStatement.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record ForStatement(AssignmentExpression? IndexerAssignment, BoolExpression? Condition, ValueExpression? IncrementExpression) : MethodBodyStatement, IEnumerable + { + private readonly List _body = new(); + public IReadOnlyList Body => _body; + + public void Add(MethodBodyStatement statement) => _body.Add(statement); + public IEnumerator GetEnumerator() => _body.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_body).GetEnumerator(); + + public override void Write(CodeWriter writer) + { + using (writer.AmbientScope()) + { + writer.AppendRaw("for ("); + IndexerAssignment?.Write(writer); + writer.AppendRaw("; "); + Condition?.Write(writer); + writer.AppendRaw("; "); + IncrementExpression?.Write(writer); + writer.WriteRawLine(")"); + + writer.WriteRawLine("{"); + writer.WriteRawLine(""); + foreach (var bodyStatement in Body) + { + bodyStatement.Write(writer); + } + writer.WriteRawLine("}"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/ForeachStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/ForeachStatement.cs new file mode 100644 index 0000000000..655b7de723 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/ForeachStatement.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record ForeachStatement(CSharpType? ItemType, CodeWriterDeclaration Item, ValueExpression Enumerable, bool IsAsync) : MethodBodyStatement, IEnumerable + { + private readonly List _body = new(); + public IReadOnlyList Body => _body; + + public ForeachStatement(CSharpType itemType, string itemName, ValueExpression enumerable, bool isAsync, out VariableReference item) + : this(itemType, new CodeWriterDeclaration(itemName), enumerable, isAsync) + { + item = new VariableReference(itemType, Item); + } + + public ForeachStatement(string itemName, EnumerableExpression enumerable, out TypedValueExpression item) + : this(null, new CodeWriterDeclaration(itemName), enumerable, false) + { + item = new VariableReference(enumerable.ItemType, Item); + } + + public ForeachStatement(string itemName, EnumerableExpression enumerable, bool isAsync, out TypedValueExpression item) + : this(null, new CodeWriterDeclaration(itemName), enumerable, isAsync) + { + item = new VariableReference(enumerable.ItemType, Item); + } + + public ForeachStatement(string itemName, DictionaryExpression dictionary, out KeyValuePairExpression item) + : this(null, new CodeWriterDeclaration(itemName), dictionary, false) + { + var variable = new VariableReference(KeyValuePairExpression.GetType(dictionary.KeyType, dictionary.ValueType), Item); + item = new KeyValuePairExpression(dictionary.KeyType, dictionary.ValueType, variable); + } + + public void Add(MethodBodyStatement statement) => _body.Add(statement); + public IEnumerator GetEnumerator() => _body.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_body).GetEnumerator(); + + public override void Write(CodeWriter writer) + { + using (writer.AmbientScope()) + { + writer.AppendRawIf("await ", IsAsync); + writer.AppendRaw("foreach ("); + if (ItemType == null) + { + writer.AppendRaw("var "); + } + else + { + writer.Append($"{ItemType} "); + } + + writer.Append($"{Item:D} in "); + Enumerable.Write(writer); + writer.WriteRawLine(")"); + + writer.WriteRawLine("{"); + foreach (var bodyStatement in Body) + { + bodyStatement.Write(writer); + } + writer.WriteRawLine("}"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/IfElsePreprocessorDirective.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/IfElsePreprocessorDirective.cs new file mode 100644 index 0000000000..3c912486b7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/IfElsePreprocessorDirective.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record IfElsePreprocessorDirective(string Condition, MethodBodyStatement If, MethodBodyStatement? Else) : MethodBodyStatement + { + public override void Write(CodeWriter writer) + { + writer.WriteLine($"#if {Condition}"); + writer.AppendRaw("\t\t\t\t"); + If.Write(writer); + if (Else is not null) + { + writer.WriteRawLine("#else"); + Else.Write(writer); + } + + writer.WriteRawLine("#endif"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/IfElseStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/IfElseStatement.cs new file mode 100644 index 0000000000..be59c636ac --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/IfElseStatement.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record IfElseStatement(IfStatement If, MethodBodyStatement? Else) : MethodBodyStatement + { + public IfElseStatement(BoolExpression condition, MethodBodyStatement ifStatement, MethodBodyStatement? elseStatement, bool inline = false, bool addBraces = true) + : this(new IfStatement(condition, inline, addBraces) { ifStatement }, elseStatement) {} + + public override void Write(CodeWriter writer) + { + If.Write(writer); + if (Else is null) + { + return; + } + + if (If.Inline || !If.AddBraces) + { + using (writer.AmbientScope()) + { + writer.AppendRaw("else "); + if (!If.Inline) + { + writer.WriteLine(); + } + Else.Write(writer); + } + } + else + { + using (writer.Scope($"else")) + { + Else.Write(writer); + } + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/IfStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/IfStatement.cs new file mode 100644 index 0000000000..7314187d18 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/IfStatement.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record IfStatement(BoolExpression Condition, bool Inline = false, bool AddBraces = true) : MethodBodyStatement, IEnumerable + { + private readonly List _body = new(); + public MethodBodyStatement Body => _body; + + public void Add(MethodBodyStatement statement) => _body.Add(statement); + public IEnumerator GetEnumerator() => _body.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_body).GetEnumerator(); + + public override void Write(CodeWriter writer) + { + writer.AppendRaw("if ("); + Condition.Write(writer); + + if (Inline) + { + writer.AppendRaw(") "); + using (writer.AmbientScope()) + { + Body.Write(writer); + } + } + else + { + writer.WriteRawLine(")"); + using (AddBraces ? writer.Scope() : writer.AmbientScope()) + { + Body.Write(writer); + } + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/InvokeInstanceMethodStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/InvokeInstanceMethodStatement.cs new file mode 100644 index 0000000000..65d21f734c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/InvokeInstanceMethodStatement.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record InvokeInstanceMethodStatement(ValueExpression? InstanceReference, string MethodName, IReadOnlyList Arguments, bool CallAsAsync) : MethodBodyStatement + { + public InvokeInstanceMethodStatement(ValueExpression? instance, string methodName) : this(instance, methodName, Array.Empty(), false) { } + public InvokeInstanceMethodStatement(ValueExpression? instance, string methodName, ValueExpression arg) : this(instance, methodName, new[] { arg }, false) { } + public InvokeInstanceMethodStatement(ValueExpression? instance, string methodName, ValueExpression arg1, ValueExpression arg2) : this(instance, methodName, new[] { arg1, arg2 }, false) { } + public InvokeInstanceMethodStatement(ValueExpression? instance, string methodName, IReadOnlyList arguments) : this(instance, methodName, arguments, false) { } + + public override void Write(CodeWriter writer) + { + new InvokeInstanceMethodExpression(InstanceReference, MethodName, Arguments, null, CallAsAsync).Write(writer); + writer.WriteRawLine(";"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/InvokeStaticMethodStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/InvokeStaticMethodStatement.cs new file mode 100644 index 0000000000..ec5448d09b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/InvokeStaticMethodStatement.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record InvokeStaticMethodStatement(CSharpType? MethodType, string MethodName, IReadOnlyList Arguments, IReadOnlyList? TypeArguments = null, bool CallAsExtension = false, bool CallAsAsync = false) : MethodBodyStatement + { + public InvokeStaticMethodStatement(CSharpType? methodType, string methodName) : this(methodType, methodName, Array.Empty()) { } + public InvokeStaticMethodStatement(CSharpType? methodType, string methodName, ValueExpression arg) : this(methodType, methodName, new[] { arg }) { } + public InvokeStaticMethodStatement(CSharpType? methodType, string methodName, ValueExpression arg1, ValueExpression arg2) : this(methodType, methodName, new[] { arg1, arg2 }) { } + public static InvokeStaticMethodStatement Extension(CSharpType? methodType, string methodName, ValueExpression instanceReference) => new(methodType, methodName, new[] { instanceReference }, CallAsExtension: true); + public static InvokeStaticMethodStatement Extension(CSharpType? methodType, string methodName, ValueExpression instanceReference, ValueExpression arg) => new(methodType, methodName, new[] { instanceReference, arg }, CallAsExtension: true); + + public override void Write(CodeWriter writer) + { + new InvokeStaticMethodExpression(MethodType, MethodName, Arguments, TypeArguments, CallAsExtension, CallAsAsync).Write(writer); + writer.WriteRawLine(";"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/KeywordStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/KeywordStatement.cs new file mode 100644 index 0000000000..c302090b1f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/KeywordStatement.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record KeywordStatement(string Keyword, ValueExpression? Expression) : MethodBodyStatement + { + public override void Write(CodeWriter writer) + { + writer.AppendRaw(Keyword); + if (Expression is not null) + { + writer.AppendRaw(" "); + Expression.Write(writer); + } + writer.WriteRawLine(";"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/MethodBodyStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/MethodBodyStatement.cs new file mode 100644 index 0000000000..7881a46043 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/MethodBodyStatement.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public record MethodBodyStatement + { + public virtual void Write(CodeWriter writer) { } + public static implicit operator MethodBodyStatement(MethodBodyStatement[] statements) => new MethodBodyStatements(Statements: statements); + public static implicit operator MethodBodyStatement(List statements) => new MethodBodyStatements(Statements: statements); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/MethodBodyStatements.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/MethodBodyStatements.cs new file mode 100644 index 0000000000..dfd6b7b576 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/MethodBodyStatements.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public record MethodBodyStatements(IReadOnlyList Statements) : MethodBodyStatement + { + public override void Write(CodeWriter writer) + { + foreach (var statement in Statements) + { + statement.Write(writer); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/SingleLineCommentStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/SingleLineCommentStatement.cs new file mode 100644 index 0000000000..bd3361e201 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/SingleLineCommentStatement.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record SingleLineCommentStatement(FormattableString Message) : MethodBodyStatement + { + public SingleLineCommentStatement(string message) : this(FormattableStringHelpers.FromString(message)) + { } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/SwitchCase.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/SwitchCase.cs new file mode 100644 index 0000000000..cef26f34d8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/SwitchCase.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record SwitchCase(IReadOnlyList Match, MethodBodyStatement Statement, bool Inline = false, bool AddScope = false) + { + public SwitchCase(ValueExpression match, MethodBodyStatement statement, bool inline = false, bool addScope = false) : this(new[] { match }, statement, inline, addScope) { } + + public static SwitchCase Default(MethodBodyStatement statement, bool inline = false, bool addScope = false) => new(Array.Empty(), statement, inline, addScope); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/SwitchStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/SwitchStatement.cs new file mode 100644 index 0000000000..2bc448a115 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/SwitchStatement.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record SwitchStatement(ValueExpression MatchExpression) : MethodBodyStatement, IEnumerable + { + public SwitchStatement(ValueExpression matchExpression, IEnumerable cases) : this(matchExpression) + { + _cases.AddRange(cases); + } + + private readonly List _cases = new(); + public IReadOnlyList Cases => _cases; + + public void Add(SwitchCase statement) => _cases.Add(statement); + public IEnumerator GetEnumerator() => _cases.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_cases).GetEnumerator(); + + public override void Write(CodeWriter writer) + { + using (writer.AmbientScope()) + { + writer.Append($"switch ("); + MatchExpression.Write(writer); + writer.WriteRawLine(")"); + writer.WriteRawLine("{"); + foreach (var switchCase in Cases) + { + if (switchCase.Match.Any()) + { + for (var i = 0; i < switchCase.Match.Count; i++) + { + ValueExpression? match = switchCase.Match[i]; + writer.AppendRaw("case "); + match.Write(writer); + if (i < switchCase.Match.Count - 1) + { + writer.WriteRawLine(":"); + } + } + } + else + { + writer.AppendRaw("default"); + } + + writer.AppendRaw(": "); + if (!switchCase.Inline) + { + writer.WriteLine(); + } + + if (switchCase.AddScope) + { + using (writer.Scope()) + { + switchCase.Statement.Write(writer); + } + } + else + { + switchCase.Statement.Write(writer); + } + } + writer.WriteRawLine("}"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/ThrowStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/ThrowStatement.cs new file mode 100644 index 0000000000..f334ecd580 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/ThrowStatement.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record ThrowStatement(ValueExpression ThrowExpression) : MethodBodyStatement + { + public override void Write(CodeWriter writer) + { + ThrowExpression.Write(writer); + writer.WriteRawLine(";"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/TryCatchFinallyStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/TryCatchFinallyStatement.cs new file mode 100644 index 0000000000..09fcd86a0e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/TryCatchFinallyStatement.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record TryCatchFinallyStatement(MethodBodyStatement Try, IReadOnlyList Catches, MethodBodyStatement? Finally) : MethodBodyStatement + { + public TryCatchFinallyStatement(MethodBodyStatement Try) : this(Try, Array.Empty(), null) + { + } + + public TryCatchFinallyStatement(MethodBodyStatement Try, CatchStatement Catch, MethodBodyStatement? Finally = null) : this(Try, [Catch], Finally) + { + } + + public override void Write(CodeWriter writer) + { + writer.WriteRawLine("try"); + writer.WriteRawLine("{"); + Try.Write(writer); + writer.WriteRawLine("}"); + + foreach (var catchStatement in Catches) + { + catchStatement.Write(writer); + } + + if (Finally != null) + { + writer.WriteRawLine("finally"); + writer.WriteRawLine("{"); + Finally.Write(writer); + writer.WriteRawLine("}"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/UnaryOperatorStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/UnaryOperatorStatement.cs new file mode 100644 index 0000000000..11dde09ebf --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/UnaryOperatorStatement.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record UnaryOperatorStatement(UnaryOperatorExpression Expression) : MethodBodyStatement + { + public override void Write(CodeWriter writer) + { + Expression.Write(writer); + writer.WriteRawLine(";"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/UsingDeclareVariableStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/UsingDeclareVariableStatement.cs new file mode 100644 index 0000000000..58d5a59747 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/UsingDeclareVariableStatement.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record UsingDeclareVariableStatement(CSharpType? Type, CodeWriterDeclaration Name, ValueExpression Value) : MethodBodyStatement + { + public override void Write(CodeWriter writer) + { + if (Type != null) + { + writer.Append($"using {Type}"); + } + else + { + writer.AppendRaw("using var"); + } + + writer.Append($" {Name:D} = "); + Value.Write(writer); + writer.WriteRawLine(";"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/UsingScopeStatement.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/UsingScopeStatement.cs new file mode 100644 index 0000000000..f3b0d90c3d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/Statements/UsingScopeStatement.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record UsingScopeStatement(CSharpType? Type, CodeWriterDeclaration Variable, ValueExpression Value) : MethodBodyStatement, IEnumerable + { + private readonly List _body = new(); + public IReadOnlyList Body => _body; + + public UsingScopeStatement(CSharpType type, string variableName, ValueExpression value, out VariableReference variable) : this(type, new CodeWriterDeclaration(variableName), value) + { + variable = new VariableReference(type, Variable); + } + + public void Add(MethodBodyStatement statement) => _body.Add(statement); + public IEnumerator GetEnumerator() => _body.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_body).GetEnumerator(); + + public override void Write(CodeWriter writer) + { + using (writer.AmbientScope()) + { + writer.AppendRaw("using ("); + if (Type == null) + { + writer.AppendRaw("var "); + } + else + { + writer.Append($"{Type} "); + } + + writer.Append($"{Variable:D} = "); + Value.Write(writer); + writer.WriteRawLine(")"); + + writer.WriteRawLine("{"); + foreach (var bodyStatement in Body) + { + bodyStatement.Write(writer); + } + writer.WriteRawLine("}"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ArrayElementExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ArrayElementExpression.cs new file mode 100644 index 0000000000..ff46fcc72b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ArrayElementExpression.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents an array element expression. + /// + /// The array. + /// The index of the element in the array. + public sealed record ArrayElementExpression(ValueExpression Array, ValueExpression Index) : ValueExpression + { + public override void Write(CodeWriter writer) + { + Array.Write(writer); + writer.AppendRaw("["); + Index.Write(writer); + writer.AppendRaw("]"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ArrayInitializerExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ArrayInitializerExpression.cs new file mode 100644 index 0000000000..2c7909ad7e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ArrayInitializerExpression.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents an array initializer expression. + /// + /// The elements to initialize the array to. + /// Flag to determine if the array should be initialized inline. + public sealed record ArrayInitializerExpression(IReadOnlyList? Elements = null, bool IsInline = true) : InitializerExpression + { + public override void Write(CodeWriter writer) + { + if (Elements is not { Count: > 0 }) + { + writer.AppendRaw("{}"); + return; + } + + if (IsInline) + { + writer.AppendRaw("{"); + foreach (var item in Elements) + { + item.Write(writer); + writer.AppendRaw(", "); + } + + writer.RemoveTrailingComma(); + writer.AppendRaw("}"); + } + else + { + writer.WriteLine(); + writer.WriteRawLine("{"); + foreach (var item in Elements) + { + item.Write(writer); + writer.WriteRawLine(","); + } + + writer.RemoveTrailingComma(); + writer.WriteLine(); + writer.AppendRaw("}"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/AsExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/AsExpression.cs new file mode 100644 index 0000000000..3b369ccdee --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/AsExpression.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record AsExpression(ValueExpression Inner, CSharpType Type) : ValueExpression + { + public override void Write(CodeWriter writer) + { + Inner.Write(writer); + writer.Append($" as {Type}"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/AssignmentExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/AssignmentExpression.cs new file mode 100644 index 0000000000..20e035e9a2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/AssignmentExpression.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents an assignment expression. + /// + /// The variable that is being assigned. + /// The value that is being assigned. + public sealed record AssignmentExpression(VariableReference Variable, ValueExpression Value) : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.Append($"{Variable.Type} {Variable.Declaration:D} = {Value}"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/BinaryDataExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/BinaryDataExpression.cs new file mode 100644 index 0000000000..dabfbf1de7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/BinaryDataExpression.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record BinaryDataExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public FrameworkTypeExpression ToObjectFromJson(Type responseType) + => new(responseType, new InvokeInstanceMethodExpression(Untyped, nameof(BinaryData.ToObjectFromJson), Array.Empty(), new[] { new CSharpType(responseType) }, false)); + + public static BinaryDataExpression FromStream(StreamExpression stream, bool async) + { + var methodName = async ? nameof(BinaryData.FromStreamAsync) : nameof(BinaryData.FromStream); + return new BinaryDataExpression(InvokeStatic(methodName, stream, async)); + } + + public static BinaryDataExpression FromStream(ValueExpression stream, bool async) + { + var methodName = async ? nameof(BinaryData.FromStreamAsync) : nameof(BinaryData.FromStream); + return new(InvokeStatic(methodName, stream, async)); + } + + public ValueExpression ToMemory() => Invoke(nameof(BinaryData.ToMemory)); + + public StreamExpression ToStream() => new(Invoke(nameof(BinaryData.ToStream))); + + public ListExpression ToArray() => new(typeof(byte[]), Invoke(nameof(BinaryData.ToArray))); + + public static BinaryDataExpression FromBytes(ValueExpression data) + => new(InvokeStatic(nameof(BinaryData.FromBytes), data)); + + public static BinaryDataExpression FromObjectAsJson(ValueExpression data) + => new(InvokeStatic(nameof(BinaryData.FromObjectAsJson), data)); + + public static BinaryDataExpression FromString(ValueExpression data) + => new(InvokeStatic(nameof(BinaryData.FromString), data)); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/BinaryOperatorExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/BinaryOperatorExpression.cs new file mode 100644 index 0000000000..7063c62e60 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/BinaryOperatorExpression.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record BinaryOperatorExpression(string Operator, ValueExpression Left, ValueExpression Right) : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.AppendRaw("("); + Left.Write(writer); + writer.AppendRaw(" ").AppendRaw(Operator).AppendRaw(" "); + Right.Write(writer); + writer.AppendRaw(")"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/BoolExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/BoolExpression.cs new file mode 100644 index 0000000000..b0e460c653 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/BoolExpression.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record BoolExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public BoolExpression Or(ValueExpression other) => new(new BinaryOperatorExpression(" || ", this, other)); + + public BoolExpression And(ValueExpression other) => new(new BinaryOperatorExpression(" && ", this, other)); + + public static BoolExpression True => new(new ConstantExpression(new Constant(true, typeof(bool)))); + + public static BoolExpression False => new(new ConstantExpression(new Constant(false, typeof(bool)))); + + public static BoolExpression Is(ValueExpression untyped, CSharpType comparisonType) => new(new BinaryOperatorExpression("is", untyped, comparisonType)); + + public static BoolExpression Is(ValueExpression untyped, DeclarationExpression declaration) => new(new BinaryOperatorExpression("is", untyped, declaration)); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CancellationTokenExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CancellationTokenExpression.cs new file mode 100644 index 0000000000..717c1f8ea9 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CancellationTokenExpression.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record CancellationTokenExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public BoolExpression CanBeCanceled => new(Property(nameof(CancellationToken.CanBeCanceled))); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CastExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CastExpression.cs new file mode 100644 index 0000000000..f84e4e5cea --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CastExpression.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents a cast expression. + /// + /// The inner value expression that is being casted. + /// The type to cast to. + public sealed record CastExpression(ValueExpression Inner, CSharpType Type) : ValueExpression + { + public override void Write(CodeWriter writer) + { + // wrap the cast expression with parenthesis, so that it would not cause ambiguity for leading recursive calls + // if the parenthesis are not needed, the roslyn reducer will remove it. + writer.AppendRaw("("); + writer.Append($"({Type})"); + Inner.Write(writer); + writer.AppendRaw(")"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CharExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CharExpression.cs new file mode 100644 index 0000000000..acb53099f9 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CharExpression.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record CharExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public StringExpression InvokeToString(ValueExpression cultureInfo) => new(Invoke(nameof(char.ToString), cultureInfo)); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CollectionInitializerExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CollectionInitializerExpression.cs new file mode 100644 index 0000000000..7bc5c24c9e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/CollectionInitializerExpression.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents a collection initializer expression. + /// + /// The items to set during collection initialization. + public sealed record CollectionInitializerExpression(params ValueExpression[] Items) : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.AppendRaw("{ "); + foreach (var item in Items) + { + item.Write(writer); + writer.AppendRaw(","); + } + + writer.RemoveTrailingComma(); + writer.AppendRaw(" }"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ConstantExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ConstantExpression.cs new file mode 100644 index 0000000000..af60b44293 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ConstantExpression.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record ConstantExpression(Constant Constant) : TypedValueExpression(Constant.Type, new UntypedConstantExpression(Constant)) + { + private record UntypedConstantExpression(Constant Constant) : ValueExpression + { + public override void Write(CodeWriter writer) => writer.Append(Constant.GetConstantFormattable()); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DateTimeOffsetExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DateTimeOffsetExpression.cs new file mode 100644 index 0000000000..e9488aad0d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DateTimeOffsetExpression.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record DateTimeOffsetExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public static DateTimeOffsetExpression Now => new(StaticProperty(nameof(DateTimeOffset.Now))); + public static DateTimeOffsetExpression UtcNow => new(StaticProperty(nameof(DateTimeOffset.UtcNow))); + + public static DateTimeOffsetExpression FromUnixTimeSeconds(ValueExpression expression) + => new(InvokeStatic(nameof(DateTimeOffset.FromUnixTimeSeconds), expression)); + + public StringExpression InvokeToString(StringExpression format, ValueExpression formatProvider) + => new(Invoke(nameof(DateTimeOffset.ToString), new[] { format, formatProvider })); + + public LongExpression ToUnixTimeSeconds() + => new(Invoke(nameof(DateTimeOffset.ToUnixTimeSeconds))); + + public DateTimeOffsetExpression ToUniversalTime() + => new(Invoke(nameof(DateTimeOffset.ToUniversalTime))); + + public static DateTimeOffsetExpression Parse(string s) => Parse(Snippets.Literal(s)); + + public static DateTimeOffsetExpression Parse(ValueExpression value) + => new(InvokeStatic(nameof(DateTimeOffset.Parse), value)); + + public static DateTimeOffsetExpression Parse(ValueExpression value, ValueExpression formatProvider, ValueExpression style) + => new(InvokeStatic(nameof(DateTimeOffset.Parse), new[] { value, formatProvider, style })); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DeclarationExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DeclarationExpression.cs new file mode 100644 index 0000000000..d823756478 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DeclarationExpression.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record DeclarationExpression(VariableReference Variable, bool IsOut) : ValueExpression + { + public DeclarationExpression(CSharpType type, string name, out VariableReference variable, bool isOut = false) : this(new VariableReference(type, name), isOut) + { + variable = Variable; + } + + public override void Write(CodeWriter writer) + { + writer.AppendRawIf("out ", IsOut); + writer.Append($"{Variable.Type} {Variable.Declaration:D}"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DictionaryExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DictionaryExpression.cs new file mode 100644 index 0000000000..f6f6e675d8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DictionaryExpression.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record DictionaryExpression(CSharpType KeyType, CSharpType ValueType, ValueExpression Untyped) : TypedValueExpression(new CSharpType(typeof(Dictionary<,>), false, KeyType, ValueType), Untyped) + { + public MethodBodyStatement Add(ValueExpression key, ValueExpression value) + => new InvokeInstanceMethodStatement(Untyped, nameof(Dictionary.Add), key, value); + + public MethodBodyStatement Add(KeyValuePairExpression pair) + => new InvokeInstanceMethodStatement(Untyped, nameof(Dictionary.Add), pair); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DictionaryInitializerExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DictionaryInitializerExpression.cs new file mode 100644 index 0000000000..746694e6dd --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DictionaryInitializerExpression.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record DictionaryInitializerExpression(IReadOnlyList<(ValueExpression Key, ValueExpression Value)>? Values = null) : InitializerExpression + { + public override void Write(CodeWriter writer) + { + if (Values is not { Count: > 0 }) + { + writer.AppendRaw("{}"); + return; + } + + writer.WriteLine(); + writer.WriteRawLine("{"); + foreach (var (key, value) in Values) + { + writer.AppendRaw("["); + key.Write(writer); + writer.AppendRaw("] = "); + value.Write(writer); + writer.WriteRawLine(","); + } + + writer.RemoveTrailingComma(); + writer.WriteLine(); + writer.AppendRaw("}"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DoubleExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DoubleExpression.cs new file mode 100644 index 0000000000..c0d3b50bb0 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/DoubleExpression.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record DoubleExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public static DoubleExpression MaxValue => new(StaticProperty(nameof(double.MaxValue))); + + public static BoolExpression IsNan(ValueExpression d) => new(new InvokeStaticMethodExpression(typeof(double), nameof(double.IsNaN), new[] { d })); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/EnumExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/EnumExpression.cs new file mode 100644 index 0000000000..a70444dbe3 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/EnumExpression.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record EnumExpression(EnumType EnumType, ValueExpression Untyped) : TypedValueExpression(EnumType.Type, Untyped) + { + public TypedValueExpression ToSerial() + => EnumType.SerializationMethodName is {} name + ? EnumType.IsExtensible + ? new FrameworkTypeExpression(EnumType.ValueType.FrameworkType, Untyped.Invoke(name)) + : new FrameworkTypeExpression(EnumType.ValueType.FrameworkType, new InvokeStaticMethodExpression(EnumType.Type, name, new[] { Untyped }, null, true)) + : EnumType is { IsExtensible: true, IsStringValueType: true } + ? Untyped.InvokeToString() + : throw new InvalidOperationException($"No conversion available fom {EnumType.Type.Name}"); + + public static TypedValueExpression ToEnum(EnumType enumType, ValueExpression value) + => enumType.IsExtensible + ? new EnumExpression(enumType, Snippets.New.Instance(enumType.Type, value)) + : new EnumExpression(enumType, new InvokeStaticMethodExpression(enumType.Type, $"To{enumType.Name}", new[] { value }, null, true)); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/EnumerableExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/EnumerableExpression.cs new file mode 100644 index 0000000000..0968927525 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/EnumerableExpression.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record EnumerableExpression(CSharpType ItemType, ValueExpression Untyped) : TypedValueExpression(new CSharpType(typeof(IEnumerable<>), false, ItemType), Untyped) + { + public BoolExpression Any() => new(new InvokeStaticMethodExpression(typeof(Enumerable), nameof(Enumerable.Any), new[] { Untyped }, CallAsExtension: true)); + public EnumerableExpression Select(TypedFuncExpression selector) => new(selector.Inner.Type, new InvokeStaticMethodExpression(typeof(Enumerable), nameof(Enumerable.Select), new[] { Untyped, selector }, CallAsExtension: true)); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/EnvironmentExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/EnvironmentExpression.cs new file mode 100644 index 0000000000..875fe67dac --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/EnvironmentExpression.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record EnvironmentExpression(ValueExpression Untyped) : TypedValueExpression(typeof(Environment), Untyped) + { + public static StringExpression NewLine() => new(new TypeReference(typeof(Environment)).Property(nameof(Environment.NewLine))); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FormattableStringExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FormattableStringExpression.cs new file mode 100644 index 0000000000..05536ccc95 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FormattableStringExpression.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents a FormattableString literal expression such as $"foo{bar}" + /// Constructed by a format string which should look like "foo{0}" + /// and a list of arguments of ValueExpressions + /// The constructor throws IndexOutOfRangeException when the count of arguments does not match in the format string and argument list. + /// + public sealed record FormattableStringExpression : ValueExpression + { + public FormattableStringExpression(string format, IReadOnlyList args) + { +#if DEBUG + Validate(format, args); +#endif + Format = format; + Args = args; + } + + public FormattableStringExpression(string format, params ValueExpression[] args) : this(format, args as IReadOnlyList) + { + } + + public string Format { get; init; } + public IReadOnlyList Args { get; init; } + + public void Deconstruct(out string format, out IReadOnlyList args) + { + format = Format; + args = Args; + } + + public override void Write(CodeWriter writer) + { + writer.AppendRaw("$\""); + var argumentCount = 0; + foreach ((var span, bool isLiteral) in StringExtensions.GetPathParts(Format)) + { + if (isLiteral) + { + writer.AppendRaw(span.ToString()); + continue; + } + + var arg = Args[argumentCount]; + argumentCount++; + // append the argument + writer.AppendRaw("{"); + arg.Write(writer); + writer.AppendRaw("}"); + } + writer.AppendRaw("\""); + } + + private static void Validate(string format, IReadOnlyList args) + { + var count = 0; + foreach (var (_, isLiteral) in StringExtensions.GetPathParts(format)) + { + if (!isLiteral) + count++; + } + + if (count != args.Count) + { + throw new IndexOutOfRangeException($"The count of arguments in format {format} does not match with the arguments provided"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FormattableStringToExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FormattableStringToExpression.cs new file mode 100644 index 0000000000..af373ddb44 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FormattableStringToExpression.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record FormattableStringToExpression(FormattableString Value) : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.Append(Value); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FrameworkTypeExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FrameworkTypeExpression.cs new file mode 100644 index 0000000000..8ce0e51fd2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FrameworkTypeExpression.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents expression which has a return value of a framework type. + /// + public sealed record FrameworkTypeExpression(Type FrameworkType, ValueExpression Untyped) : TypedValueExpression(FrameworkType, Untyped); +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FuncExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FuncExpression.cs new file mode 100644 index 0000000000..2b8aff53ab --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/FuncExpression.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record FuncExpression(IReadOnlyList Parameters, ValueExpression Inner) : ValueExpression + { + public override void Write(CodeWriter writer) + { + using (writer.AmbientScope()) + { + if (Parameters.Count == 1) + { + var parameter = Parameters[0]; + if (parameter is not null) + { + writer.WriteDeclaration(parameter); + } + else + { + writer.AppendRaw("_"); + } + } + else + { + writer.AppendRaw("("); + foreach (var parameter in Parameters) + { + if (parameter is not null) + { + writer.WriteDeclaration(parameter); + } + else + { + writer.AppendRaw("_"); + } + writer.AppendRaw(", "); + } + + writer.RemoveTrailingComma(); + writer.AppendRaw(")"); + } + + writer.AppendRaw(" => "); + Inner.Write(writer); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/IndexerExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/IndexerExpression.cs new file mode 100644 index 0000000000..c8a47af558 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/IndexerExpression.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record IndexerExpression(ValueExpression? Inner, ValueExpression Index) : ValueExpression + { + public override void Write(CodeWriter writer) + { + if (Inner is not null) + { + Inner.Write(writer); + } + writer.AppendRaw("["); + Index.Write(writer); + writer.AppendRaw("]"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/InitializerExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/InitializerExpression.cs new file mode 100644 index 0000000000..cb18b94550 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/InitializerExpression.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public record InitializerExpression : ValueExpression; +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/IntExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/IntExpression.cs new file mode 100644 index 0000000000..6b8d62e329 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/IntExpression.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record IntExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public static IntExpression MaxValue => new(StaticProperty(nameof(int.MaxValue))); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/InvokeInstanceMethodExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/InvokeInstanceMethodExpression.cs new file mode 100644 index 0000000000..ac89b63609 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/InvokeInstanceMethodExpression.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record InvokeInstanceMethodExpression(ValueExpression? InstanceReference, string MethodName, IReadOnlyList Arguments, IReadOnlyList? TypeArguments, bool CallAsAsync, bool AddConfigureAwaitFalse = true) : ValueExpression + { + public InvokeInstanceMethodExpression(ValueExpression? instanceReference, MethodSignature signature, IReadOnlyList arguments, bool addConfigureAwaitFalse = true) : this(instanceReference, signature.Name, arguments, signature.GenericArguments, signature.Modifiers.HasFlag(MethodSignatureModifiers.Async), addConfigureAwaitFalse) { } + + public InvokeInstanceMethodExpression(ValueExpression? instanceReference, MethodSignature signature, bool addConfigureAwaitFalse = true) : this(instanceReference, signature, signature.Parameters.Select(p => (ValueExpression)p).ToArray(), addConfigureAwaitFalse) { } + + internal MethodBodyStatement ToStatement() + => new InvokeInstanceMethodStatement(InstanceReference, MethodName, Arguments, CallAsAsync); + + public override void Write(CodeWriter writer) + { + writer.AppendRawIf("await ", CallAsAsync); + if (InstanceReference != null) + { + InstanceReference.Write(writer); + writer.AppendRaw("."); + } + + writer.AppendRaw(MethodName); + writer.WriteTypeArguments(TypeArguments); + writer.WriteArguments(Arguments); + writer.AppendRawIf(".ConfigureAwait(false)", CallAsAsync && AddConfigureAwaitFalse); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/InvokeStaticMethodExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/InvokeStaticMethodExpression.cs new file mode 100644 index 0000000000..3c740ea779 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/InvokeStaticMethodExpression.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record InvokeStaticMethodExpression(CSharpType? MethodType, string MethodName, IReadOnlyList Arguments, IReadOnlyList? TypeArguments = null, bool CallAsExtension = false, bool CallAsAsync = false) : ValueExpression + { + public static InvokeStaticMethodExpression Extension(CSharpType? methodType, string methodName, ValueExpression instanceReference) => new(methodType, methodName, new[] { instanceReference }, CallAsExtension: true); + public static InvokeStaticMethodExpression Extension(CSharpType? methodType, string methodName, ValueExpression instanceReference, ValueExpression arg) => new(methodType, methodName, new[] { instanceReference, arg }, CallAsExtension: true); + public static InvokeStaticMethodExpression Extension(CSharpType? methodType, string methodName, ValueExpression instanceReference, IReadOnlyList arguments) + => new(methodType, methodName, arguments.Prepend(instanceReference).ToArray(), CallAsExtension: true); + + public MethodBodyStatement ToStatement() + => new InvokeStaticMethodStatement(MethodType, MethodName, Arguments, TypeArguments, CallAsExtension, CallAsAsync); + + public override void Write(CodeWriter writer) + { + if (CallAsExtension) + { + writer.AppendRawIf("await ", CallAsAsync); + if (MethodType != null) + { + writer.UseNamespace(MethodType.Namespace); + } + + Arguments[0].Write(writer); + writer.AppendRaw("."); + writer.AppendRaw(MethodName); + writer.WriteTypeArguments(TypeArguments); + writer.WriteArguments(Arguments.Skip(1)); + writer.AppendRawIf(".ConfigureAwait(false)", CallAsAsync); + } + else + { + writer.AppendRawIf("await ", CallAsAsync); + if (MethodType != null) + { + writer.Append($"{MethodType}."); + } + + writer.AppendRaw(MethodName); + writer.WriteTypeArguments(TypeArguments); + writer.WriteArguments(Arguments); + writer.AppendRawIf(".ConfigureAwait(false)", CallAsAsync); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonDocumentExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonDocumentExpression.cs new file mode 100644 index 0000000000..b46f0157e9 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonDocumentExpression.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record JsonDocumentExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public JsonElementExpression RootElement => new(Property(nameof(JsonDocument.RootElement))); + + public static JsonDocumentExpression ParseValue(ValueExpression reader) => new(InvokeStatic(nameof(JsonDocument.ParseValue), reader)); + public static JsonDocumentExpression Parse(ValueExpression json) => new(InvokeStatic(nameof(JsonDocument.Parse), json)); + public static JsonDocumentExpression Parse(BinaryDataExpression binaryData) => new(InvokeStatic(nameof(JsonDocument.Parse), binaryData)); + public static JsonDocumentExpression Parse(StreamExpression stream) => new(InvokeStatic(nameof(JsonDocument.Parse), stream)); + + public static JsonDocumentExpression Parse(StreamExpression stream, bool async) + { + // Sync and async methods have different set of parameters + return async + ? new JsonDocumentExpression(InvokeStatic(nameof(JsonDocument.ParseAsync), new[] { stream, Snippets.Default, KnownParameters.CancellationTokenParameter }, true)) + : new JsonDocumentExpression(InvokeStatic(nameof(JsonDocument.Parse), stream)); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonElementExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonElementExpression.cs new file mode 100644 index 0000000000..5b402c0d48 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonElementExpression.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; +using static Microsoft.Generator.CSharp.Expressions.Snippets; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record JsonElementExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public JsonValueKindExpression ValueKind => new(Property(nameof(JsonElement.ValueKind))); + public EnumerableExpression EnumerateArray() => new(typeof(JsonElement), Invoke(nameof(JsonElement.EnumerateArray))); + public EnumerableExpression EnumerateObject() => new(typeof(JsonProperty), Invoke(nameof(JsonElement.EnumerateObject))); + public JsonElementExpression this[int index] => new(new IndexerExpression(Untyped, Int(index))); + public JsonElementExpression GetProperty(string propertyName) => new(Invoke(nameof(JsonElement.GetProperty), Literal(propertyName))); + + public ValueExpression InvokeClone() => Invoke(nameof(JsonElement.Clone)); + public ValueExpression GetArrayLength() => Invoke(nameof(JsonElement.GetArrayLength)); + public ValueExpression GetBoolean() => Invoke(nameof(JsonElement.GetBoolean)); + public ValueExpression GetBytesFromBase64() => Invoke(nameof(JsonElement.GetBytesFromBase64)); + public ValueExpression GetBytesFromBase64(string? format) => Extensible.JsonElement.GetBytesFromBase64(this, format); + public ValueExpression GetChar() => Extensible.JsonElement.GetChar(this); + public ValueExpression GetDateTimeOffset(string? format) => Extensible.JsonElement.GetDateTimeOffset(this, format); + public ValueExpression GetDateTime() => Invoke(nameof(JsonElement.GetDateTime)); + public ValueExpression GetDecimal() => Invoke(nameof(JsonElement.GetDecimal)); + public ValueExpression GetDouble() => Invoke(nameof(JsonElement.GetDouble)); + public ValueExpression GetGuid() => Invoke(nameof(JsonElement.GetGuid)); + public ValueExpression GetSByte() => Invoke(nameof(JsonElement.GetSByte)); + public ValueExpression GetByte() => Invoke(nameof(JsonElement.GetByte)); + public ValueExpression GetInt16() => Invoke(nameof(JsonElement.GetInt16)); + public ValueExpression GetInt32() => Invoke(nameof(JsonElement.GetInt32)); + public ValueExpression GetInt64() => Invoke(nameof(JsonElement.GetInt64)); + public ValueExpression GetObject() => Extensible.JsonElement.GetObject(this); + public StringExpression GetRawText() => new(Invoke(nameof(JsonElement.GetRawText))); + public ValueExpression GetSingle() => Untyped.Invoke(nameof(JsonElement.GetSingle)); + public StringExpression GetString() => new(Untyped.Invoke(nameof(JsonElement.GetString))); + public ValueExpression GetTimeSpan(string? format) => Extensible.JsonElement.GetTimeSpan(this, format); + + public BoolExpression ValueKindEqualsNull() + => new(new BinaryOperatorExpression("==", Property(nameof(JsonElement.ValueKind)), FrameworkEnumValue(JsonValueKind.Null))); + + public BoolExpression ValueKindNotEqualsUndefined() + => new(new BinaryOperatorExpression("!=", Property(nameof(JsonElement.ValueKind)), FrameworkEnumValue(JsonValueKind.Undefined))); + + public BoolExpression ValueKindEqualsString() + => new(new BinaryOperatorExpression("==", Property(nameof(JsonElement.ValueKind)), FrameworkEnumValue(JsonValueKind.String))); + + public MethodBodyStatement WriteTo(ValueExpression writer) => new InvokeInstanceMethodStatement(Untyped, nameof(JsonElement.WriteTo), new[] { writer }, false); + + public BoolExpression TryGetProperty(string propertyName, out JsonElementExpression discriminator) + { + var discriminatorDeclaration = new VariableReference(typeof(JsonElement), "discriminator"); + discriminator = new JsonElementExpression(discriminatorDeclaration); + var invocation = new InvokeInstanceMethodExpression(this, nameof(JsonElement.TryGetProperty), new ValueExpression[] { Literal(propertyName), new DeclarationExpression(discriminatorDeclaration, true) }, null, false); + return new BoolExpression(invocation); + } + + public BoolExpression TryGetInt32(out IntExpression intValue) + { + var intValueDeclaration = new VariableReference(typeof(int), "intValue"); + intValue = new IntExpression(intValueDeclaration); + var invocation = new InvokeInstanceMethodExpression(this, nameof(JsonElement.TryGetInt32), new ValueExpression[] { new DeclarationExpression(intValueDeclaration, true) }, null, false); + return new BoolExpression(invocation); + } + + public BoolExpression TryGetInt64(out LongExpression longValue) + { + var longValueDeclaration = new VariableReference(typeof(long), "longValue"); + longValue = new LongExpression(longValueDeclaration); + var invocation = new InvokeInstanceMethodExpression(this, nameof(JsonElement.TryGetInt64), new ValueExpression[] { new DeclarationExpression(longValueDeclaration, true) }, null, false); + return new BoolExpression(invocation); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonPropertyExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonPropertyExpression.cs new file mode 100644 index 0000000000..609aeb92dd --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonPropertyExpression.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; +using static Microsoft.Generator.CSharp.Expressions.Snippets; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record JsonPropertyExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public StringExpression Name => new(Property(nameof(JsonProperty.Name))); + public JsonElementExpression Value => new(Property(nameof(JsonProperty.Value))); + + public BoolExpression NameEquals(string value) => new(Invoke(nameof(JsonProperty.NameEquals), LiteralU8(value))); + + public MethodBodyStatement ThrowNonNullablePropertyIsNull() => Extensible.JsonElement.ThrowNonNullablePropertyIsNull(this); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonSerializerExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonSerializerExpression.cs new file mode 100644 index 0000000000..c63bbb5974 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonSerializerExpression.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public static class JsonSerializerExpression + { + public static InvokeStaticMethodExpression Serialize(ValueExpression writer, ValueExpression value, ValueExpression? options = null) + { + var arguments = options is null + ? new[] { writer, value } + : new[] { writer, value, options }; + return new InvokeStaticMethodExpression(typeof(JsonSerializer), nameof(JsonSerializer.Serialize), arguments); + } + + public static InvokeStaticMethodExpression Deserialize(JsonElementExpression element, CSharpType serializationType, ValueExpression? options = null) + { + var arguments = options is null + ? new[] { element.GetRawText() } + : new[] { element.GetRawText(), options }; + return new InvokeStaticMethodExpression(typeof(JsonSerializer), nameof(JsonSerializer.Deserialize), arguments, new[] { serializationType }); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonValueKindExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonValueKindExpression.cs new file mode 100644 index 0000000000..e38861f38b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/JsonValueKindExpression.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record JsonValueKindExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public static JsonValueKindExpression String => InvokeStaticProperty(nameof(JsonValueKind.String)); + public static JsonValueKindExpression Number => InvokeStaticProperty(nameof(JsonValueKind.Number)); + public static JsonValueKindExpression True => InvokeStaticProperty(nameof(JsonValueKind.True)); + public static JsonValueKindExpression False => InvokeStaticProperty(nameof(JsonValueKind.False)); + public static JsonValueKindExpression Undefined => InvokeStaticProperty(nameof(JsonValueKind.Undefined)); + public static JsonValueKindExpression Null => InvokeStaticProperty(nameof(JsonValueKind.Null)); + public static JsonValueKindExpression Array => InvokeStaticProperty(nameof(JsonValueKind.Array)); + public static JsonValueKindExpression Object => InvokeStaticProperty(nameof(JsonValueKind.Object)); + + private static JsonValueKindExpression InvokeStaticProperty(string name) + => new(new MemberExpression(typeof(JsonValueKind), name)); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/KeyValuePairExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/KeyValuePairExpression.cs new file mode 100644 index 0000000000..b29fbfc8e2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/KeyValuePairExpression.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record KeyValuePairExpression(CSharpType KeyType, CSharpType ValueType, ValueExpression Untyped) : TypedValueExpression(GetType(KeyType, ValueType), Untyped) + { + public TypedValueExpression Key => new TypedMemberExpression(Untyped, nameof(KeyValuePair.Key), KeyType); + public TypedValueExpression Value => new TypedMemberExpression(Untyped, nameof(KeyValuePair.Value), ValueType); + + public static CSharpType GetType(CSharpType keyType, CSharpType valueType) => new(typeof(KeyValuePair<,>), false, keyType, valueType); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/KeywordExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/KeywordExpression.cs new file mode 100644 index 0000000000..17ee2d0dfd --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/KeywordExpression.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record KeywordExpression(string Keyword, ValueExpression? Expression) : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.AppendRaw(Keyword); + if (Expression is not null) + { + writer.AppendRaw(" "); + Expression.Write(writer); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ListExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ListExpression.cs new file mode 100644 index 0000000000..e65ebf311b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ListExpression.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record ListExpression(CSharpType ItemType, ValueExpression Untyped) : TypedValueExpression(new CSharpType(typeof(List<>), ItemType), Untyped) + { + public MethodBodyStatement Add(ValueExpression item) => new InvokeInstanceMethodStatement(Untyped, nameof(List.Add), item); + + public ValueExpression ToArray() => Invoke(nameof(List.ToArray)); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/LongExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/LongExpression.cs new file mode 100644 index 0000000000..a34a822c04 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/LongExpression.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record LongExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public StringExpression InvokeToString(ValueExpression formatProvider) + => new(Invoke(nameof(long.ToString), formatProvider)); + + public static LongExpression Parse(StringExpression value, ValueExpression formatProvider) + => new(new InvokeStaticMethodExpression(typeof(long), nameof(long.Parse), new[] { value, formatProvider })); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/MemberExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/MemberExpression.cs new file mode 100644 index 0000000000..ead594e97c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/MemberExpression.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record MemberExpression(ValueExpression? Inner, string MemberName) : ValueExpression + { + public override void Write(CodeWriter writer) + { + if (Inner is not null) + { + Inner.Write(writer); + writer.AppendRaw("."); + } + writer.AppendRaw(MemberName); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewArrayExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewArrayExpression.cs new file mode 100644 index 0000000000..7bb87b5ae0 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewArrayExpression.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record NewArrayExpression(CSharpType? Type, ArrayInitializerExpression? Items = null, ValueExpression? Size = null) : ValueExpression + { + public override void Write(CodeWriter writer) + { + if (Size is not null) + { + if (Type is null) + { + writer.AppendRaw("new["); + } + else + { + writer.Append($"new {Type}["); + } + + Size.Write(writer); + writer.AppendRaw("]"); + return; + } + + if (Items is { Elements.Count: > 0 }) + { + if (Type is null) + { + writer.AppendRaw("new[]"); + } + else + { + writer.Append($"new {Type}[]"); + } + + Items.Write(writer); + } + else if (Type is null) + { + writer.AppendRaw("new[]{}"); + } + else + { + writer.Append($"Array.Empty<{Type}>()"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewDictionaryExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewDictionaryExpression.cs new file mode 100644 index 0000000000..ba5e28f40b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewDictionaryExpression.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record NewDictionaryExpression(CSharpType Type, DictionaryInitializerExpression? Values = null) : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.Append($"new {Type}"); + if (Values is { Values.Count: > 0 }) + { + Values.Write(writer); + } + else + { + writer.AppendRaw("()"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewInstanceExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewInstanceExpression.cs new file mode 100644 index 0000000000..64e3c9ca22 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewInstanceExpression.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record NewInstanceExpression(CSharpType Type, IReadOnlyList Parameters, ObjectInitializerExpression? InitExpression = null) : ValueExpression + { + private const int SingleLineParameterThreshold = 6; + + public override void Write(CodeWriter writer) + { + writer.Append($"new {Type}"); + if (Parameters.Count > 0 || InitExpression is not { Parameters.Count: > 0 }) + { + writer.WriteArguments(Parameters, Parameters.Count < SingleLineParameterThreshold); + } + + if (InitExpression is { Parameters.Count: > 0 }) + { + InitExpression.Write(writer); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewJsonSerializerOptionsExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewJsonSerializerOptionsExpression.cs new file mode 100644 index 0000000000..b082955407 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NewJsonSerializerOptionsExpression.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record NewJsonSerializerOptionsExpression : ValueExpression { } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NullConditionalExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NullConditionalExpression.cs new file mode 100644 index 0000000000..edfb2c9025 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/NullConditionalExpression.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record NullConditionalExpression(ValueExpression Inner) : ValueExpression + { + public override void Write(CodeWriter writer) + { + Inner.Write(writer); + writer.AppendRaw("?"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ObjectInitializerExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ObjectInitializerExpression.cs new file mode 100644 index 0000000000..67bead7b02 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ObjectInitializerExpression.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents an object initializer expression. + /// + /// The parameters to initialize the object to. + /// Flag to determine if the object should be initialized inline. + public sealed record ObjectInitializerExpression(IReadOnlyDictionary? Parameters = null, bool UseSingleLine = true) : InitializerExpression + { + public override void Write(CodeWriter writer) + { + if (Parameters is not { Count: > 0 }) + { + writer.AppendRaw("{}"); + return; + } + + if (UseSingleLine) + { + writer.AppendRaw("{"); + foreach (var (name, value) in Parameters) + { + writer.Append($"{name} = "); + value.Write(writer); + writer.AppendRaw(", "); + } + + writer.RemoveTrailingComma(); + writer.AppendRaw("}"); + } + else + { + writer.WriteLine(); + writer.WriteRawLine("{"); + foreach (var (name, value) in Parameters) + { + writer.Append($"{name} = "); + value.Write(writer); + writer.WriteRawLine(","); + } + + writer.AppendRaw("}"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ObjectTypeExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ObjectTypeExpression.cs new file mode 100644 index 0000000000..f406c13a34 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ObjectTypeExpression.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record ObjectTypeExpression(TypeProvider TypeProvider, ValueExpression Untyped) : TypedValueExpression(TypeProvider.Type, Untyped) + { + public static MemberExpression FromResponseDelegate(TypeProvider typeProvider) + => new(new TypeReference(typeProvider.Type), CodeModelPlugin.Instance.Configuration.ApiTypes.FromResponseName); + + public static MemberExpression DeserializeDelegate(TypeProvider typeProvider) + => new(new TypeReference(typeProvider.Type), $"Deserialize{typeProvider.Name}"); + + public static ObjectTypeExpression Deserialize(TypeProvider typeProvider, ValueExpression element, ValueExpression? options = null) + { + var arguments = options == null ? new[] { element } : new[] { element, options }; + return new(typeProvider, new InvokeStaticMethodExpression(typeProvider.Type, $"Deserialize{typeProvider.Name}", arguments)); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ParameterReference.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ParameterReference.cs new file mode 100644 index 0000000000..11bddb7051 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ParameterReference.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record ParameterReference(Parameter Parameter) : TypedValueExpression(Parameter.Type, new UntypedParameterReference(Parameter)) + { + private record UntypedParameterReference(Parameter Parameter) : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.AppendRawIf("ref ", Parameter.IsRef); + writer.Append($"{Parameter.Name:I}"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/PositionalParameterReference.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/PositionalParameterReference.cs new file mode 100644 index 0000000000..d06f57e632 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/PositionalParameterReference.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record PositionalParameterReference(string ParameterName, ValueExpression ParameterValue) : ValueExpression + { + internal PositionalParameterReference(Parameter parameter) : this(parameter.Name, parameter) { } + + public override void Write(CodeWriter writer) + { + writer.Append($"{ParameterName}: "); + ParameterValue.Write(writer); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StreamExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StreamExpression.cs new file mode 100644 index 0000000000..885d7ffc85 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StreamExpression.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record StreamExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + internal MethodBodyStatement CopyTo(StreamExpression destination) => new InvokeInstanceMethodStatement(Untyped, nameof(Stream.CopyTo), destination); + + internal ValueExpression Position => new TypedMemberExpression(this, nameof(Stream.Position), typeof(long)); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StreamReaderExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StreamReaderExpression.cs new file mode 100644 index 0000000000..92fe3e8e26 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StreamReaderExpression.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record StreamReaderExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public StringExpression ReadToEnd(bool async) + { + var methodName = async ? nameof(StreamReader.ReadToEndAsync) : nameof(StreamReader.ReadToEnd); + return new(Invoke(methodName, Array.Empty(), async)); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StringBuilderExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StringBuilderExpression.cs new file mode 100644 index 0000000000..b8dec661bb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StringBuilderExpression.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record StringBuilderExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public StringExpression Length => new(Property(nameof(StringBuilder.Length))); + + public MethodBodyStatement Append(StringExpression value) => new InvokeInstanceMethodStatement(Untyped, nameof(StringBuilder.Append), value); + + public MethodBodyStatement AppendLine(StringExpression value) => new InvokeInstanceMethodStatement(Untyped, nameof(StringBuilder.AppendLine), value); + + public MethodBodyStatement Append(ValueExpression value) => new InvokeInstanceMethodStatement(Untyped, nameof(StringBuilder.Append), value); + + public MethodBodyStatement AppendLine(ValueExpression value) => new InvokeInstanceMethodStatement(Untyped, nameof(StringBuilder.AppendLine), value); + + public MethodBodyStatement Append(string value) => Append(Snippets.Literal(value)); + + public MethodBodyStatement AppendLine(string value) => AppendLine(Snippets.Literal(value)); + + public MethodBodyStatement Append(FormattableStringExpression value) => new InvokeInstanceMethodStatement(Untyped, nameof(StringBuilder.Append), value); + + public MethodBodyStatement AppendLine(FormattableStringExpression value) => new InvokeInstanceMethodStatement(Untyped, nameof(StringBuilder.AppendLine), value); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StringExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StringExpression.cs new file mode 100644 index 0000000000..3d51ed4c91 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StringExpression.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record StringExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public CharExpression Index(ValueExpression index) => new(new IndexerExpression(this, index)); + public CharExpression Index(int index) => Index(Snippets.Literal(index)); + public ValueExpression Length => Property(nameof(string.Length)); + + public static BoolExpression Equals(StringExpression left, StringExpression right, StringComparison comparisonType) + => new(InvokeStatic(nameof(string.Equals), new[] { left, right, Snippets.FrameworkEnumValue(comparisonType) })); + + public static StringExpression Format(StringExpression format, params ValueExpression[] args) + => new(new InvokeStaticMethodExpression(typeof(string), nameof(string.Format), args.Prepend(format).ToArray())); + + public static BoolExpression IsNullOrWhiteSpace(StringExpression value, params ValueExpression[] args) + => new(new InvokeStaticMethodExpression(typeof(string), nameof(string.IsNullOrWhiteSpace), args.Prepend(value).ToArray())); + + public static StringExpression Join(ValueExpression separator, ValueExpression values) + => new(new InvokeStaticMethodExpression(typeof(string), nameof(string.Join), new[] { separator, values })); + + public StringExpression Substring(ValueExpression startIndex) + => new(new InvokeInstanceMethodExpression(this, nameof(string.Substring), new[] { startIndex }, null, false)); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StringLiteralExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StringLiteralExpression.cs new file mode 100644 index 0000000000..51a6d8e884 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/StringLiteralExpression.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents a string literal expression. + /// + /// The string literal. + /// Flag used to determine if the string expression should represent a UTF-8 string. + public sealed record StringLiteralExpression(string Literal, bool U8) : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.WriteLiteral(Literal); + if (U8) + { + writer.AppendRaw("u8"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/SwitchCaseExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/SwitchCaseExpression.cs new file mode 100644 index 0000000000..2875690764 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/SwitchCaseExpression.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents a switch case expression. + /// + /// The conditional case. + /// The expression. + public sealed record SwitchCaseExpression(ValueExpression Case, ValueExpression Expression) + { + public static SwitchCaseExpression When(ValueExpression caseExpression, BoolExpression condition, ValueExpression expression) + { + return new(new SwitchCaseWhenExpression(caseExpression, condition), expression); + } + public static SwitchCaseExpression Default(ValueExpression expression) => new SwitchCaseExpression(Snippets.Dash, expression); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/SwitchCaseWhenExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/SwitchCaseWhenExpression.cs new file mode 100644 index 0000000000..e136f555ec --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/SwitchCaseWhenExpression.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record SwitchCaseWhenExpression(ValueExpression Case, BoolExpression Condition) : ValueExpression + { + public override void Write(CodeWriter writer) + { + Case.Write(writer); + writer.AppendRaw(" when "); + Condition.Write(writer); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/SwitchExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/SwitchExpression.cs new file mode 100644 index 0000000000..051110ad48 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/SwitchExpression.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents a switch case expression. + /// + /// The match expression. + /// The list of cases represented in the form of . + public sealed record SwitchExpression(ValueExpression MatchExpression, params SwitchCaseExpression[] Cases) : ValueExpression + { + public override void Write(CodeWriter writer) + { + using (writer.AmbientScope()) + { + MatchExpression.Write(writer); + writer.WriteRawLine(" switch"); + writer.WriteRawLine("{"); + foreach (var switchCase in Cases) + { + switchCase.Case.Write(writer); + writer.AppendRaw(" => "); + switchCase.Expression.Write(writer); + writer.WriteRawLine(","); + } + writer.RemoveTrailingComma(); + writer.WriteLine(); + writer.AppendRaw("}"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TernaryConditionalOperator.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TernaryConditionalOperator.cs new file mode 100644 index 0000000000..64b5738159 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TernaryConditionalOperator.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents a ternary conditional operator. + /// + public sealed record TernaryConditionalOperator(ValueExpression Condition, ValueExpression Consequent, ValueExpression Alternative) : ValueExpression + { + public override void Write(CodeWriter writer) + { + Condition.Write(writer); + writer.AppendRaw(" ? "); + Consequent.Write(writer); + writer.AppendRaw(" : "); + Alternative.Write(writer); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TimeSpanExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TimeSpanExpression.cs new file mode 100644 index 0000000000..ae678b5ddb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TimeSpanExpression.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record TimeSpanExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public StringExpression InvokeToString(string? format) => new(Invoke(nameof(TimeSpan.ToString), new[] { Snippets.Literal(format) })); + public StringExpression InvokeToString(ValueExpression format, ValueExpression formatProvider) + => new(Invoke(nameof(TimeSpan.ToString), new[] { format, formatProvider })); + + public static TimeSpanExpression FromSeconds(ValueExpression value) => new(InvokeStatic(nameof(TimeSpan.FromSeconds), value)); + + public static TimeSpanExpression ParseExact(ValueExpression value, ValueExpression format, ValueExpression formatProvider) + => new(new InvokeStaticMethodExpression(typeof(TimeSpan), nameof(TimeSpan.ParseExact), new[] { value, format, formatProvider })); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypeReference.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypeReference.cs new file mode 100644 index 0000000000..a964c8b6e4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypeReference.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record TypeReference(CSharpType Type) : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.Append($"{Type}"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedFuncExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedFuncExpression.cs new file mode 100644 index 0000000000..7fc0a9022d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedFuncExpression.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record TypedFuncExpression(IReadOnlyList Parameters, TypedValueExpression Inner) : TypedValueExpression(Inner.Type, new FuncExpression(Parameters, Inner)); +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedMemberExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedMemberExpression.cs new file mode 100644 index 0000000000..a2d79a5c8c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedMemberExpression.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + internal record TypedMemberExpression(ValueExpression? Inner, string MemberName, CSharpType MemberType) : TypedValueExpression(MemberType, new MemberExpression(Inner, MemberName)); +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedNullConditionalExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedNullConditionalExpression.cs new file mode 100644 index 0000000000..366519b300 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedNullConditionalExpression.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + internal record TypedNullConditionalExpression(TypedValueExpression Inner) : TypedValueExpression(Inner.Type, new NullConditionalExpression(Inner.Untyped)); +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedTernaryConditionalOperator.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedTernaryConditionalOperator.cs new file mode 100644 index 0000000000..f036bad913 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedTernaryConditionalOperator.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + internal record TypedTernaryConditionalOperator(BoolExpression Condition, TypedValueExpression Consequent, ValueExpression Alternative) + : TypedValueExpression(Consequent.Type, new TernaryConditionalOperator(Condition, Consequent, Alternative)); +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedValueExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedValueExpression.cs new file mode 100644 index 0000000000..b79d267e61 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedValueExpression.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// A wrapper around ValueExpression that also specifies return type of the expression + /// Return type doesn't affect how expression is written, but helps creating strong-typed helpers to create value expressions. + /// + /// Type expected to be returned by value expression. + /// + public abstract record TypedValueExpression(CSharpType Type, ValueExpression Untyped) : ValueExpression + { + public override void Write(CodeWriter writer) => Untyped.Write(writer); + public static implicit operator TypedValueExpression(FieldDeclaration name) => new VariableReference(name.Type, name.Declaration); + public static implicit operator TypedValueExpression(Parameter parameter) => new ParameterReference(parameter); + + public TypedValueExpression NullableStructValue() => this is not ConstantExpression && Type is { IsNullable: true, IsValueType: true } ? new TypedMemberExpression(this, nameof(Nullable.Value), Type.WithNullable(false)) : this; + + public TypedValueExpression NullConditional() => Type.IsNullable ? new TypedNullConditionalExpression(this) : this; + + public virtual TypedValueExpression Property(string propertyName, CSharpType propertyType) + => new TypedMemberExpression(this, propertyName, propertyType); + + protected static ValueExpression ValidateType(TypedValueExpression typed, CSharpType type) + { + if (type.Equals(typed.Type, ignoreNullable: true)) + { + return typed.Untyped; + } + + throw new InvalidOperationException($"Expression with return type {typed.Type.Name} is cast to type {type.Name}"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedValueExpressionOfT.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedValueExpressionOfT.cs new file mode 100644 index 0000000000..6522340608 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/TypedValueExpressionOfT.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ +#pragma warning disable SA1649 // File name should match first type name + public abstract record TypedValueExpression(ValueExpression Untyped) : TypedValueExpression(typeof(T), ValidateType(Untyped, typeof(T))) +#pragma warning restore SA1649 // File name should match first type name + { + protected static MemberExpression StaticProperty(string name) => new(typeof(T), name); + + protected static InvokeStaticMethodExpression InvokeStatic(string methodName) + => new(typeof(T), methodName, Array.Empty(), null, false); + + protected static InvokeStaticMethodExpression InvokeStatic(string methodName, ValueExpression arg) + => new(typeof(T), methodName, new[] { arg }, null, false); + + protected static InvokeStaticMethodExpression InvokeStatic(string methodName, ValueExpression arg1, ValueExpression arg2) + => new(typeof(T), methodName, new[] { arg1, arg2 }, null, false); + + protected static InvokeStaticMethodExpression InvokeStatic(string methodName, IReadOnlyList arguments) + => new(typeof(T), methodName, arguments, null, false); + + protected static InvokeStaticMethodExpression InvokeStatic(MethodSignature method) + => new(typeof(T), method.Name, method.Parameters.Select(p => (ValueExpression)p).ToList()); + + protected static InvokeStaticMethodExpression InvokeStatic(MethodSignature method, bool async) + => new(typeof(T), method.Name, method.Parameters.Select(p => (ValueExpression)p).ToList(), CallAsAsync: async); + + protected static InvokeStaticMethodExpression InvokeStatic(string methodName, bool async) + => new(typeof(T), methodName, Array.Empty(), CallAsAsync: async); + + protected static InvokeStaticMethodExpression InvokeStatic(string methodName, ValueExpression arg, bool async) + => new(typeof(T), methodName, new[] { arg }, CallAsAsync: async); + + protected static InvokeStaticMethodExpression InvokeStatic(string methodName, IReadOnlyList arguments, bool async) + => new(typeof(T), methodName, arguments, CallAsAsync: async); + + protected InvokeStaticMethodExpression InvokeExtension(CSharpType extensionType, string methodName) + => new(extensionType, methodName, new[] { Untyped }, CallAsAsync: false, CallAsExtension: true); + + protected InvokeStaticMethodExpression InvokeExtension(CSharpType extensionType, string methodName, ValueExpression arg) + => new(extensionType, methodName, new[] { Untyped, arg }, CallAsAsync: false, CallAsExtension: true); + + protected InvokeStaticMethodExpression InvokeExtension(CSharpType extensionType, string methodName, ValueExpression arg1, ValueExpression arg2) + => new(extensionType, methodName, new[] { Untyped, arg1, arg2 }, CallAsAsync: false, CallAsExtension: true); + + protected InvokeStaticMethodExpression InvokeExtension(CSharpType extensionType, string methodName, IEnumerable arguments, bool async) + => new(extensionType, methodName, arguments.Prepend(Untyped).ToArray(), CallAsAsync: async, CallAsExtension: true); + + private static ValueExpression ValidateType(ValueExpression untyped, Type type) + { +#if DEBUG + if (untyped is not TypedValueExpression typed) + { + return untyped; + } + + if (typed.Type.IsFrameworkType) + { + if (typed.Type.FrameworkType.IsGenericTypeDefinition && type.IsGenericType) + { + if (typed.Type.FrameworkType.MakeGenericType(type.GetGenericArguments()).IsAssignableTo(type)) + { + return typed.Untyped; + } + } + else if (typed.Type.FrameworkType.IsAssignableTo(type)) + { + return typed.Untyped; + } + } + + throw new InvalidOperationException($"Expression with return type {typed.Type.Name} is cast to type {type.Name}"); +#else + return untyped; +#endif + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/UnaryOperatorExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/UnaryOperatorExpression.cs new file mode 100644 index 0000000000..ced65a70bb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/UnaryOperatorExpression.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record UnaryOperatorExpression(string Operator, ValueExpression Operand, bool OperandOnTheLeft) : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.AppendRawIf(Operator, !OperandOnTheLeft); + Operand.Write(writer); + writer.AppendRawIf(Operator, OperandOnTheLeft); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/Utf8JsonWriterExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/Utf8JsonWriterExpression.cs new file mode 100644 index 0000000000..b6766e5bf2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/Utf8JsonWriterExpression.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using static Microsoft.Generator.CSharp.Expressions.Snippets; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record Utf8JsonWriterExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public LongExpression BytesCommitted => new(Property(nameof(Utf8JsonWriter.BytesCommitted))); + public LongExpression BytesPending => new(Property(nameof(Utf8JsonWriter.BytesPending))); + + public MethodBodyStatement WriteStartObject() => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WriteStartObject)); + public MethodBodyStatement WriteEndObject() => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WriteEndObject)); + public MethodBodyStatement WriteStartArray(ValueExpression name) => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WriteStartArray), name); + public MethodBodyStatement WriteStartArray() => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WriteStartArray)); + public MethodBodyStatement WriteEndArray() => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WriteEndArray)); + public MethodBodyStatement WritePropertyName(string propertyName) => WritePropertyName(LiteralU8(propertyName)); + public MethodBodyStatement WritePropertyName(ValueExpression propertyName) => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WritePropertyName), propertyName); + public MethodBodyStatement WriteNull(string propertyName) => WriteNull(Literal(propertyName)); + public MethodBodyStatement WriteNull(ValueExpression propertyName) => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WriteNull), propertyName); + public MethodBodyStatement WriteNullValue() => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WriteNullValue)); + + public MethodBodyStatement WriteObjectValue(ValueExpression value) + => new InvokeStaticMethodStatement(CodeModelPlugin.Instance.Configuration.ApiTypes.Utf8JsonWriterExtensionsType, CodeModelPlugin.Instance.Configuration.ApiTypes.Utf8JsonWriterExtensionsWriteObjectValueName, new[] { Untyped, value }, null, true); + + public MethodBodyStatement WriteNumberValue(ValueExpression value) + => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WriteNumberValue), value); + + public MethodBodyStatement WriteStringValue(ValueExpression value) + => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WriteStringValue), value); + + public MethodBodyStatement WriteBooleanValue(ValueExpression value) + => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WriteBooleanValue), value); + + public MethodBodyStatement WriteRawValue(ValueExpression value) + => new InvokeInstanceMethodStatement(Untyped, nameof(Utf8JsonWriter.WriteRawValue), value); + + public MethodBodyStatement WriteBase64StringValue(ValueExpression value) + => Invoke(nameof(Utf8JsonWriter.WriteBase64StringValue), value).ToStatement(); + + public MethodBodyStatement WriteBinaryData(ValueExpression value) + => new IfElsePreprocessorDirective + ( + "NET6_0_OR_GREATER", + WriteRawValue(value), + new UsingScopeStatement(typeof(JsonDocument), "document", JsonDocumentExpression.Parse(value), out var jsonDocumentVar) + { + JsonSerializerExpression.Serialize(this, new JsonDocumentExpression(jsonDocumentVar).RootElement).ToStatement() + } + ); + + public MethodBodyStatement Flush() + => new InvokeInstanceMethodStatement(this, nameof(Utf8JsonWriter.Flush), Array.Empty(), false); + + public MethodBodyStatement FlushAsync(ValueExpression? cancellationToken = null) + { + var arguments = cancellationToken is null + ? Array.Empty() + : new[] { cancellationToken }; + return new InvokeInstanceMethodStatement(this, nameof(Utf8JsonWriter.FlushAsync), arguments, true); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ValueExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ValueExpression.cs new file mode 100644 index 0000000000..92f7d71f6a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/ValueExpression.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ + /// + /// Represents a single operator or operand, or a sequence of operators or operands. + /// + public record ValueExpression + { + public virtual void Write(CodeWriter writer) { } + + public static implicit operator ValueExpression(Type type) => new TypeReference(type); + public static implicit operator ValueExpression(CSharpType type) => new TypeReference(type); + public static implicit operator ValueExpression(Parameter parameter) => new ParameterReference(parameter); + public static implicit operator ValueExpression(FieldDeclaration name) => new VariableReference(name.Type, name.Declaration); + public static implicit operator ValueExpression(PropertyDeclaration name) => new VariableReference(name.PropertyType, name.Declaration); + + public ValueExpression NullableStructValue(CSharpType candidateType) => this is not ConstantExpression && candidateType is { IsNullable: true, IsValueType: true } ? new MemberExpression(this, nameof(Nullable.Value)) : this; + public StringExpression InvokeToString() => new(Invoke(nameof(ToString))); + public ValueExpression InvokeGetType() => Invoke(nameof(GetType)); + + public BoolExpression InvokeEquals(ValueExpression other) => new(Invoke(nameof(Equals), other)); + + public virtual ValueExpression Property(string propertyName, bool nullConditional = false) + => new MemberExpression(nullConditional ? new NullConditionalExpression(this) : this, propertyName); + + public InvokeInstanceMethodExpression Invoke(string methodName) + => new InvokeInstanceMethodExpression(this, methodName, Array.Empty(), null, false); + + public InvokeInstanceMethodExpression Invoke(string methodName, ValueExpression arg) + => new InvokeInstanceMethodExpression(this, methodName, new[] { arg }, null, false); + + public InvokeInstanceMethodExpression Invoke(string methodName, ValueExpression arg1, ValueExpression arg2) + => new InvokeInstanceMethodExpression(this, methodName, new[] { arg1, arg2 }, null, false); + + public InvokeInstanceMethodExpression Invoke(string methodName, IReadOnlyList arguments) + => new InvokeInstanceMethodExpression(this, methodName, arguments, null, false); + + public InvokeInstanceMethodExpression Invoke(MethodSignature method) + => new InvokeInstanceMethodExpression(this, method.Name, method.Parameters.Select(p => (ValueExpression)p).ToList(), null, method.Modifiers.HasFlag(MethodSignatureModifiers.Async)); + + public InvokeInstanceMethodExpression Invoke(MethodSignature method, IReadOnlyList arguments, bool addConfigureAwaitFalse = true) + => new InvokeInstanceMethodExpression(this, method.Name, arguments, null, method.Modifiers.HasFlag(MethodSignatureModifiers.Async), AddConfigureAwaitFalse: addConfigureAwaitFalse); + + public InvokeInstanceMethodExpression Invoke(string methodName, bool async) + => new InvokeInstanceMethodExpression(this, methodName, Array.Empty(), null, async); + + public InvokeInstanceMethodExpression Invoke(string methodName, IReadOnlyList arguments, bool async) + => new InvokeInstanceMethodExpression(this, methodName, arguments, null, async); + + public CastExpression CastTo(CSharpType to) => new CastExpression(this, to); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/VariableReference.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/VariableReference.cs new file mode 100644 index 0000000000..e00e6cb96d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/VariableReference.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record VariableReference(CSharpType Type, CodeWriterDeclaration Declaration) : TypedValueExpression(Type, new FormattableStringToExpression($"{Declaration:I}")) + { + public VariableReference(CSharpType type, string name) : this(type, new CodeWriterDeclaration(name)) { } + + private record UntypedVariableReference(CodeWriterDeclaration Declaration) : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.Append(Declaration); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/WhereExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/WhereExpression.cs new file mode 100644 index 0000000000..5be16e5e84 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/WhereExpression.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record WhereExpression(CSharpType Type, IReadOnlyList Constraints) : ValueExpression + { + public WhereExpression(CSharpType type, ValueExpression constraint) : this(type, new[] { constraint }) { } + + public WhereExpression And(ValueExpression constraint) => new(Type, new List(Constraints) { constraint }); + + public override void Write(CodeWriter writer) + { + writer + .AppendRaw("where ") + .Append($"{Type} : "); + foreach (var constraint in Constraints) + { + constraint.Write(writer); + writer.AppendRaw(","); + } + writer.RemoveTrailingComma(); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XAttributeExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XAttributeExpression.cs new file mode 100644 index 0000000000..de795b4a7f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XAttributeExpression.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Xml.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record XAttributeExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public XNameExpression Name => new(Property(nameof(XAttribute.Name))); + public StringExpression Value => new(Property(nameof(XAttribute.Value))); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XDocumentExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XDocumentExpression.cs new file mode 100644 index 0000000000..da3dc34b20 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XDocumentExpression.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Xml.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record XDocumentExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public static XDocumentExpression Load(StreamExpression stream, LoadOptions loadOptions) + => new(InvokeStatic(nameof(XDocument.Load), stream, Snippets.FrameworkEnumValue(loadOptions))); + + public XElementExpression Element(string name) => new(Invoke(nameof(XDocument.Element), Snippets.Literal(name))); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XElementExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XElementExpression.cs new file mode 100644 index 0000000000..30cdee232e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XElementExpression.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Xml.Linq; +using static Microsoft.Generator.CSharp.Expressions.Snippets; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record XElementExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public XNameExpression Name => new(Property(nameof(XElement.Name))); + public StringExpression Value => new(Property(nameof(XElement.Value))); + + public XAttributeExpression Attribute(string name) + => new(Invoke(nameof(XElement.Attribute), Literal(name))); + + public ValueExpression GetBytesFromBase64Value(string? format) => Extensible.XElement.GetBytesFromBase64Value(this, format); + public ValueExpression GetDateTimeOffsetValue(string? format) => Extensible.XElement.GetDateTimeOffsetValue(this, format); + public ValueExpression GetObjectValue(string? format) => Extensible.XElement.GetObjectValue(this, format); + public ValueExpression GetTimeSpanValue(string? format) => Extensible.XElement.GetTimeSpanValue(this, format); + + public static XElementExpression Load(StreamExpression stream) => new(new InvokeStaticMethodExpression(typeof(XElement), nameof(XElement.Load), new[] { stream })); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XNameExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XNameExpression.cs new file mode 100644 index 0000000000..16a6396973 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XNameExpression.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Xml.Linq; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record XNameExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public StringExpression LocalName => new(Property(nameof(XName.LocalName))); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XmlWriterExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XmlWriterExpression.cs new file mode 100644 index 0000000000..27b3f7bd9c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/ValueExpressions/XmlWriterExpression.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Xml; +using static Microsoft.Generator.CSharp.Expressions.Snippets; + +namespace Microsoft.Generator.CSharp.Expressions +{ + public sealed record XmlWriterExpression(ValueExpression Untyped) : TypedValueExpression(Untyped) + { + public MethodBodyStatement WriteStartAttribute(string localName) => new InvokeInstanceMethodStatement(Untyped, nameof(XmlWriter.WriteStartAttribute), Literal(localName)); + public MethodBodyStatement WriteEndAttribute() => new InvokeInstanceMethodStatement(Untyped, nameof(XmlWriter.WriteEndAttribute)); + + public MethodBodyStatement WriteStartElement(string localName) => WriteStartElement(Literal(localName)); + public MethodBodyStatement WriteStartElement(ValueExpression localName) => new InvokeInstanceMethodStatement(Untyped, nameof(XmlWriter.WriteStartElement), localName); + public MethodBodyStatement WriteEndElement() => new InvokeInstanceMethodStatement(Untyped, nameof(XmlWriter.WriteEndElement)); + + public MethodBodyStatement WriteValue(ValueExpression value) => new InvokeInstanceMethodStatement(Untyped, nameof(XmlWriter.WriteValue), value); + public MethodBodyStatement WriteValue(ValueExpression value, string format) => Extensible.XmlWriter.WriteValue(this, value, format); + + public MethodBodyStatement WriteValue(ValueExpression value, Type frameworkType, SerializationFormat format) + { + bool writeFormat = frameworkType == typeof(byte[]) || + frameworkType == typeof(DateTimeOffset) || + frameworkType == typeof(DateTime) || + frameworkType == typeof(TimeSpan); + return writeFormat && format.ToFormatSpecifier() is { } formatSpecifier + ? WriteValue(value, formatSpecifier) + : WriteValue(value); + } + + public MethodBodyStatement WriteObjectValue(ValueExpression value, string? nameHint) => Extensible.XmlWriter.WriteObjectValue(this, value, nameHint); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/GeneratedCodeWorkspace.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/GeneratedCodeWorkspace.cs new file mode 100644 index 0000000000..9169b4c451 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/GeneratedCodeWorkspace.cs @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Simplification; + +namespace Microsoft.Generator.CSharp +{ + internal class GeneratedCodeWorkspace + { + public static readonly string GeneratedFolder = Constants.DefaultGeneratedCodeFolderName; + public static readonly string GeneratedTestFolder = Constants.DefaultGeneratedTestFolderName; + + private static readonly Lazy> _assemblyMetadataReferences = new(() => new List() + { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }); + private static readonly Lazy _metadataReferenceResolver = new(() => new WorkspaceMetadataReferenceResolver()); + private static readonly CSharpSyntaxRewriter SA1505Rewriter = new SA1505Rewriter(); + private static Task? _cachedProject; + private static readonly string[] _generatedFolders = { GeneratedFolder }; + private static readonly string _newLine = "\n"; + + private Project _project; + private Dictionary PlainFiles { get; } + + private GeneratedCodeWorkspace(Project generatedCodeProject) + { + _project = generatedCodeProject; + PlainFiles = new(); + } + + /// + /// Creating AdHoc workspace and project takes a while, we'd like to preload this work + /// to the generator startup time + /// + public static void Initialize() + { + _cachedProject = Task.Run(CreateGeneratedCodeProject); + } + + public void AddPlainFiles(string name, string content) + { + PlainFiles.Add(name, content); + } + + public async Task AddGeneratedFile(string name, string text) + { + var document = _project.AddDocument(name, text, _generatedFolders); + var root = await document.GetSyntaxRootAsync(); + Debug.Assert(root != null); + + root = root.WithAdditionalAnnotations(Simplifier.Annotation); + document = document.WithSyntaxRoot(root); + _project = document.Project; + } + + public async IAsyncEnumerable<(string Name, string Text)> GetGeneratedFilesAsync() + { + List> documents = new List>(); + foreach (Document document in _project.Documents) + { + if (!IsGeneratedDocument(document)) + { + continue; + } + + documents.Add(ProcessDocument(document)); + } + var docs = await Task.WhenAll(documents); + + foreach (var doc in docs) + { + var processed = doc; + + var text = await processed.GetSyntaxTreeAsync(); + yield return (processed.Name, text!.ToString()); + } + + foreach (var (file, content) in PlainFiles) + { + yield return (file, content); + } + } + + private async Task ProcessDocument(Document document) + { + var syntaxTree = await document.GetSyntaxTreeAsync(); + if (syntaxTree != null) + { + var root = await syntaxTree.GetRootAsync(); + document = document.WithSyntaxRoot(SA1505Rewriter.Visit(root)); + } + + document = await Simplifier.ReduceAsync(document); + document = await Formatter.FormatAsync(document); + return document; + } + + public static bool IsGeneratedDocument(Document document) => document.Folders.Contains(GeneratedFolder); + + /// + /// Create a new AdHoc workspace using the Roslyn SDK and add a project with all the necessary compilation options. + /// + /// The created project in the solution. + private static Project CreateGeneratedCodeProject() + { + var workspace = new AdhocWorkspace(); + var newOptionSet = workspace.Options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, _newLine); + workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(newOptionSet)); + Project generatedCodeProject = workspace.AddProject(Constants.DefaultGeneratedCodeProjectFolderName, LanguageNames.CSharp); + + generatedCodeProject = generatedCodeProject + .AddMetadataReferences(_assemblyMetadataReferences.Value) + .WithCompilationOptions(new CSharpCompilationOptions( + OutputKind.DynamicallyLinkedLibrary, metadataReferenceResolver: _metadataReferenceResolver.Value, nullableContextOptions: NullableContextOptions.Disable)); + return generatedCodeProject; + } + + internal static async Task Create() + { + var projectTask = Interlocked.Exchange(ref _cachedProject, null); + var generatedCodeProject = projectTask != null ? await projectTask : CreateGeneratedCodeProject(); + + var outputDirectory = CodeModelPlugin.Instance.Configuration.OutputDirectory; + var projectDirectory = CodeModelPlugin.Instance.Configuration.ProjectDirectory; + + if (Path.IsPathRooted(projectDirectory) && Path.IsPathRooted(outputDirectory)) + { + projectDirectory = Path.GetFullPath(projectDirectory); + outputDirectory = Path.GetFullPath(outputDirectory); + + Directory.CreateDirectory(projectDirectory); + Directory.CreateDirectory(outputDirectory); + + generatedCodeProject = AddDirectory(generatedCodeProject, projectDirectory, skipPredicate: sourceFile => sourceFile.StartsWith(outputDirectory)); + } + + generatedCodeProject = generatedCodeProject.WithParseOptions(new CSharpParseOptions(preprocessorSymbols: new[] { "EXPERIMENTAL" })); + return new GeneratedCodeWorkspace(generatedCodeProject); + } + + public static GeneratedCodeWorkspace CreateExistingCodeProject(string outputDirectory) + { + var workspace = new AdhocWorkspace(); + var newOptionSet = workspace.Options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, _newLine); + workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(newOptionSet)); + Project project = workspace.AddProject("ExistingCode", LanguageNames.CSharp); + + if (Path.IsPathRooted(outputDirectory)) + { + outputDirectory = Path.GetFullPath(outputDirectory); + project = AddDirectory(project, outputDirectory, null); + } + + project = project + .AddMetadataReferences(_assemblyMetadataReferences.Value) + .WithCompilationOptions(new CSharpCompilationOptions( + OutputKind.DynamicallyLinkedLibrary, metadataReferenceResolver: _metadataReferenceResolver.Value, nullableContextOptions: NullableContextOptions.Disable)); + + return new GeneratedCodeWorkspace(project); + } + + public static async Task CreatePreviousContractFromDll(string xmlDocumentationpath, string dllPath) + { + var workspace = new AdhocWorkspace(); + Project project = workspace.AddProject("PreviousContract", LanguageNames.CSharp); + project = project + .AddMetadataReferences(_assemblyMetadataReferences.Value) + .WithCompilationOptions(new CSharpCompilationOptions( + OutputKind.DynamicallyLinkedLibrary, metadataReferenceResolver: _metadataReferenceResolver.Value, nullableContextOptions: NullableContextOptions.Disable)); + project = project.AddMetadataReference(MetadataReference.CreateFromFile(dllPath, documentation: XmlDocumentationProvider.CreateFromFile(xmlDocumentationpath))); + return await project.GetCompilationAsync(); + } + + /// + /// Add the files in the directory to a project per a given predicate with the folders specified + /// + /// + /// + /// + /// + /// The instance with the added directory and files. + internal static Project AddDirectory(Project project, string directory, Func? skipPredicate = null, IEnumerable? folders = null) + { + foreach (string sourceFile in Directory.GetFiles(directory, "*.cs", SearchOption.AllDirectories)) + { + if (skipPredicate != null && skipPredicate(sourceFile)) + continue; + + project = project.AddDocument(sourceFile, File.ReadAllText(sourceFile), folders ?? Array.Empty(), sourceFile).Project; + } + + return project; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/GeneratorContext.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/GeneratorContext.cs new file mode 100644 index 0000000000..3b0b870ad7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/GeneratorContext.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp +{ + public class GeneratorContext + { + internal GeneratorContext(Configuration configuration) + { + Configuration = configuration; + } + + public Configuration Configuration { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/KnownParameters.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/KnownParameters.cs new file mode 100644 index 0000000000..f15e8729c0 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/KnownParameters.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using Microsoft.Generator.CSharp.Input; + +namespace Microsoft.Generator.CSharp +{ + public partial class KnownParameters + { + public TypeFactory TypeFactory { get; } + public KnownParameters(TypeFactory typeFactory) + { + TypeFactory = typeFactory; + } + + private static readonly CSharpType RequestContentType = new(CodeModelPlugin.Instance.Configuration.ApiTypes.RequestContentType); + private static readonly CSharpType RequestContentNullableType = new(CodeModelPlugin.Instance.Configuration.ApiTypes.RequestContentType, true); + private static readonly CSharpType RequestContextType = new(CodeModelPlugin.Instance.Configuration.ApiTypes.RequestContextType); + private static readonly CSharpType RequestContextNullableType = new(CodeModelPlugin.Instance.Configuration.ApiTypes.RequestContextType, true); + private static readonly CSharpType ResponseType = new(CodeModelPlugin.Instance.Configuration.ApiTypes.ResponseType); + + public Parameter TokenAuth => new("tokenCredential", $"The token credential to copy", TypeFactory.TokenCredentialType(), null, ValidationType.None, null); + public Parameter PageSizeHint => new("pageSizeHint", $"The number of items per {TypeFactory.PageResponseType():C} that should be requested (from service operations that support it). It's not guaranteed that the value will be respected.", new CSharpType(typeof(int), true), null, ValidationType.None, null); + public Parameter MatchConditionsParameter => new("matchConditions", $"The content to send as the request conditions of the request.", TypeFactory.MatchConditionsType(), Constant.Default(TypeFactory.RequestConditionsType()), ValidationType.None, null, RequestLocation: RequestLocation.Header); + public Parameter RequestConditionsParameter => new("requestConditions", $"The content to send as the request conditions of the request.", TypeFactory.RequestConditionsType(), Constant.Default(TypeFactory.RequestConditionsType()), ValidationType.None, null, RequestLocation: RequestLocation.Header); + + public static readonly Parameter ClientDiagnostics = new("clientDiagnostics", $"The handler for diagnostic messaging in the client.", new CSharpType(CodeModelPlugin.Instance.Configuration.ApiTypes.ClientDiagnosticsType), null, ValidationType.AssertNotNull, null); + public static readonly Parameter Pipeline = new("pipeline", $"The HTTP pipeline for sending and receiving REST requests and responses", new CSharpType(CodeModelPlugin.Instance.Configuration.ApiTypes.HttpPipelineType), null, ValidationType.AssertNotNull, null); + public static readonly Parameter KeyAuth = new("keyCredential", $"The key credential to copy", new CSharpType(CodeModelPlugin.Instance.Configuration.ApiTypes.KeyCredentialType), null, ValidationType.None, null); + public static readonly Parameter Endpoint = new("endpoint", $"Service endpoint", new CSharpType(typeof(Uri)), null, ValidationType.None, null, RequestLocation: RequestLocation.Uri, IsEndpoint: true); + + public static readonly Parameter NextLink = new("nextLink", $"Continuation token", typeof(string), null, ValidationType.None, null); + + public static readonly Parameter RequestContent = new("content", $"The content to send as the body of the request.", RequestContentType, null, ValidationType.AssertNotNull, null, RequestLocation: RequestLocation.Body); + public static readonly Parameter RequestContentNullable = new("content", $"The content to send as the body of the request.", RequestContentNullableType, null, ValidationType.None, null, RequestLocation: RequestLocation.Body); + + public static readonly Parameter RequestContext = new("context", $"The request context, which can override default behaviors of the client pipeline on a per-call basis.", RequestContextNullableType, Constant.Default(RequestContextNullableType), ValidationType.None, null); + public static readonly Parameter RequestContextRequired = new("context", $"The request context, which can override default behaviors of the client pipeline on a per-call basis.", RequestContextType, null, ValidationType.None, null); + + public static readonly Parameter CancellationTokenParameter = new("cancellationToken", $"The cancellation token to use", new CSharpType(typeof(CancellationToken)), Constant.NewInstanceOf(typeof(CancellationToken)), ValidationType.None, null); + public static readonly Parameter EnumeratorCancellationTokenParameter = new("cancellationToken", $"Enumerator cancellation token", typeof(CancellationToken), Constant.NewInstanceOf(typeof(CancellationToken)), ValidationType.None, null) { Attributes = new[] { new CSharpAttribute(typeof(EnumeratorCancellationAttribute)) } }; + + public static readonly Parameter Response = new("response", $"Response returned from backend service", ResponseType, null, ValidationType.None, null); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Microsoft.Generator.CSharp.csproj b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Microsoft.Generator.CSharp.csproj new file mode 100644 index 0000000000..a0356e01f0 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Microsoft.Generator.CSharp.csproj @@ -0,0 +1,24 @@ + + + + Microsoft.Generator.CSharp + Exe + net8.0 + enable + + + + + + + + + + + + + + + + + diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/AutoPropertyBody.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/AutoPropertyBody.cs new file mode 100644 index 0000000000..939c02dd94 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/AutoPropertyBody.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp +{ + internal record AutoPropertyBody(bool HasSetter, MethodSignatureModifiers SetterModifiers = MethodSignatureModifiers.None, ConstantExpression? InitializationExpression = null) : PropertyBody; +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Constant.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Constant.cs new file mode 100644 index 0000000000..a213f14d12 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Constant.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System; + +namespace Microsoft.Generator.CSharp +{ + public readonly struct Constant + { + internal static object NewInstanceSentinel { get; } = new object(); + + public Constant(object? value, CSharpType type) + { + Value = value; + Type = type; + + if (value == null) + { + if (!type.IsNullable) + { + throw new InvalidOperationException($"Null constant with non-nullable type {type}"); + } + } + + if (value == NewInstanceSentinel || value is Expression) + { + return; + } + + // TODO: Re-enable this check once we have enum type support + //if (!type.IsFrameworkType && + // type.Implementation is EnumType && + // value != null && + // !(value is EnumTypeValue || value is string)) + //{ + // throw new InvalidOperationException($"Unexpected value '{value}' for enum type '{type}'"); + //} + + if (value != null && type.IsFrameworkType && value.GetType() != type.FrameworkType) + { + throw new InvalidOperationException($"Constant type mismatch. Value type is '{value.GetType()}'. CSharpType is '{type}'."); + } + } + + public object? Value { get; } + public CSharpType Type { get; } + public bool IsNewInstanceSentinel => Value == NewInstanceSentinel; + + internal static Constant NewInstanceOf(CSharpType type) + { + return new Constant(NewInstanceSentinel, type); + } + + internal static Constant FromExpression(FormattableString expression, CSharpType type) => new Constant(new Expression(expression), type); + + internal static Constant Default(CSharpType type) + => type.IsValueType && !type.IsNullable ? new Constant(NewInstanceSentinel, type) : new Constant(null, type); + + /// + /// A value type. It represents an expression without any reference (e.g. 'DateTimeOffset.Now') + /// which looks like a constant. + /// + internal class Expression + { + internal Expression(FormattableString expressionValue) + { + ExpressionValue = expressionValue; + } + + public FormattableString ExpressionValue { get; } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/ConstructorInitializer.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/ConstructorInitializer.cs new file mode 100644 index 0000000000..19f93fa794 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/ConstructorInitializer.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp +{ + public sealed record ConstructorInitializer(bool IsBase, IReadOnlyList Arguments) + { + public ConstructorInitializer(bool isBase, IEnumerable arguments) : this(isBase, arguments.Select(p => (ValueExpression)p).ToArray()) { } + + public ConstructorInitializer(bool isBase, IEnumerable arguments) : this(isBase, arguments.Select(r => new FormattableStringToExpression(FormattableStringFactory.Create("{0:I}", r.Name))).ToArray()) { } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/ConstructorSignature.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/ConstructorSignature.cs new file mode 100644 index 0000000000..a3a9ffb478 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/ConstructorSignature.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp +{ + /// + /// Represents the signature of a constructor in C#. + /// + /// The type of the constructor. + /// The summary of the constructor. + /// The description of the constructor. + /// The modifiers of the constructor. + /// The parameters of the constructor. + /// The attributes of the constructor. + /// The initializer of the constructor. + public sealed record ConstructorSignature( + CSharpType Type, + FormattableString? Summary, + FormattableString? Description, + MethodSignatureModifiers Modifiers, + IReadOnlyList Parameters, + IReadOnlyList? Attributes = null, + ConstructorInitializer? Initializer = null) + : MethodSignatureBase(Type.Name, Summary, Description, null, Modifiers, Parameters, Attributes ?? Array.Empty()); +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/ExpressionPropertyBody.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/ExpressionPropertyBody.cs new file mode 100644 index 0000000000..c650423adf --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/ExpressionPropertyBody.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.Models +{ + internal record ExpressionPropertyBody(ValueExpression Getter) : PropertyBody; +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/FieldDeclaration.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/FieldDeclaration.cs new file mode 100644 index 0000000000..3981bae91e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/FieldDeclaration.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp +{ + public sealed record FieldDeclaration(FormattableString? Description, FieldModifiers Modifiers, CSharpType Type, CSharpType ValueType, CodeWriterDeclaration Declaration, ValueExpression? InitializationValue, bool IsRequired, SerializationFormat SerializationFormat, bool IsField = false, bool WriteAsProperty = false, bool OptionalViaNullability = false, FieldModifiers? GetterModifiers = null, FieldModifiers? SetterModifiers = null, SourcePropertySerializationMapping? SerializationMapping = null) + { + public string Name => Declaration.ActualName; + public string Accessibility => (Modifiers & FieldModifiers.Public) > 0 ? "public" : "internal"; + + public FieldDeclaration(FieldModifiers modifiers, CSharpType type, string name, bool writeAsProperty = false) + : this(description: null, + modifiers: modifiers, + type: type, + name: name, + serializationFormat: SerializationFormat.Default, + writeAsProperty: writeAsProperty) + { } + + public FieldDeclaration(FormattableString description, FieldModifiers modifiers, CSharpType type, string name, ValueExpression? initializationValue = null) + : this(Description: description, + Modifiers: modifiers, + Type: type, + ValueType: type, + Declaration: new CodeWriterDeclaration(name), + IsRequired: false, + InitializationValue: initializationValue, + SerializationFormat: SerializationFormat.Default) + { } + + public FieldDeclaration(FieldModifiers modifiers, CSharpType type, string name, ValueExpression? initializationValue, SerializationFormat serializationFormat, bool writeAsProperty = false) + : this(Description: null, + Modifiers: modifiers, + Type: type, + ValueType: type, + Declaration: new CodeWriterDeclaration(name), + InitializationValue: initializationValue, + IsRequired: false, + SerializationFormat: serializationFormat, + IsField: false, + WriteAsProperty: writeAsProperty, + GetterModifiers: null, + SetterModifiers: null) + { } + + public FieldDeclaration(FormattableString? description, FieldModifiers modifiers, CSharpType type, string name, SerializationFormat serializationFormat, bool writeAsProperty = false) + : this(Description: description, + Modifiers: modifiers, + Type: type, + ValueType: type, + Declaration: new CodeWriterDeclaration(name), + InitializationValue: null, + IsRequired: false, + SerializationFormat: serializationFormat, + IsField: false, + WriteAsProperty: writeAsProperty) + { } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/FieldModifiers.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/FieldModifiers.cs new file mode 100644 index 0000000000..b2949b9dc8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/FieldModifiers.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp +{ + [Flags] + public enum FieldModifiers + { + Public = 1 << 0, + Internal = 1 << 1, + Protected = 1 << 2, + Private = 1 << 3, + Static = 1 << 4, + ReadOnly = 1 << 5, + Const = 1 << 6 + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/IndexerDeclaration.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/IndexerDeclaration.cs new file mode 100644 index 0000000000..255ff1e189 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/IndexerDeclaration.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp +{ + internal record IndexerDeclaration(FormattableString? Description, MethodSignatureModifiers Modifiers, CSharpType PropertyType, string Name, Parameter IndexerParameter, PropertyBody PropertyBody, IReadOnlyDictionary? Exceptions = null) + : PropertyDeclaration(Description, Modifiers, PropertyType, Name, PropertyBody, Exceptions); +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Method.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Method.cs new file mode 100644 index 0000000000..4dcc57b81d --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Method.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp +{ + /// + /// Represents a method consisting of a signature, body, and expression. + /// + public sealed record Method + { + // The method kind. + public string Kind { get; } + public MethodSignatureBase Signature { get; } + public MethodBodyStatement? Body { get; } + public ValueExpression? BodyExpression { get; } + + /// + /// Initializes a new instance of the class with a body statement and method signature. + /// + /// The method signature. + /// The method body. + /// The method kind. + public Method(MethodSignatureBase signature, MethodBodyStatement body, string kind) + { + Signature = signature; + Body = body; + Kind = kind; + } + + /// + /// Initializes a new instance of the class with a body expression and method signature. + /// + /// The method signature. + /// The method body expression. + /// The method kind. + public Method(MethodSignatureBase signature, ValueExpression bodyExpression, string kind) + { + Signature = signature; + BodyExpression = bodyExpression; + Kind = kind; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodPropertyBody.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodPropertyBody.cs new file mode 100644 index 0000000000..f8784e1612 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodPropertyBody.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.Models +{ + internal record MethodPropertyBody(MethodBodyStatement Getter, MethodBodyStatement? Setter = null, MethodSignatureModifiers SetterModifiers = MethodSignatureModifiers.None) : PropertyBody; +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodSignature.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodSignature.cs new file mode 100644 index 0000000000..51d3c7b63f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodSignature.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp +{ + /// + /// Represents a method signature. + /// + /// The name of the method. + /// The summary of the method. + /// The description of the method. + /// The modifiers of the method. + /// The return type of the method. + /// The description of the return value. + /// The parameters of the method. + /// The attributes of the method. + /// The generic arguments of the method. + /// The generic parameter constraints of the method. + /// The explicit interface of the method. + /// The non-document comment of the method. + /// Indicates if the summary text is raw. + public sealed record MethodSignature(string Name, FormattableString? Summary, FormattableString? Description, MethodSignatureModifiers Modifiers, CSharpType? ReturnType, FormattableString? ReturnDescription, IReadOnlyList Parameters, IReadOnlyList? Attributes = null, IReadOnlyList? GenericArguments = null, IReadOnlyList? GenericParameterConstraints = null, CSharpType? ExplicitInterface = null, string? NonDocumentComment = null, bool IsRawSummaryText = false) + : MethodSignatureBase(Name, Summary, Description, NonDocumentComment, Modifiers, Parameters, Attributes ?? Array.Empty(), IsRawSummaryText: IsRawSummaryText) + { + public static IEqualityComparer ParameterAndReturnTypeEqualityComparer = new MethodSignatureParameterAndReturnTypeEqualityComparer(); + + /// + /// Returns a new instance of MethodSignature with all required parameters. + /// + public MethodSignature WithParametersRequired() + { + if (Parameters.All(p => p.DefaultValue is null)) + { + return this; + } + return this with { Parameters = Parameters.Select(p => p.ToRequired()).ToList() }; + } + + /// + /// Gets the C# reference string for the method. + /// + public FormattableString GetCRef() => $"{Name}({Parameters.GetTypesFormattable()})"; + + private class MethodSignatureParameterAndReturnTypeEqualityComparer : IEqualityComparer + { + public bool Equals(MethodSignature? x, MethodSignature? y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (x is null || y is null) + { + return false; + } + + var result = x.Name == x.Name + && x.ReturnType == y.ReturnType + && x.Parameters.SequenceEqual(y.Parameters, Parameter.TypeAndNameEqualityComparer); + return result; + } + + public int GetHashCode([DisallowNull] MethodSignature obj) + { + return HashCode.Combine(obj.Name, obj.ReturnType); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodSignatureBase.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodSignatureBase.cs new file mode 100644 index 0000000000..6b01913cb7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodSignatureBase.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp +{ + /// + /// Represents the base class for a method signature. + /// + /// The name of the method. + /// The summary of the method. + /// The description of the method. + /// The non-document comment of the method. + /// The modifiers of the method. + /// The parameters of the method. + /// The attributes of the method. + /// A flag indicating if the summary text is raw. + public abstract record MethodSignatureBase(string Name, FormattableString? Summary, FormattableString? Description, string? NonDocumentComment, MethodSignatureModifiers Modifiers, IReadOnlyList Parameters, IReadOnlyList Attributes, bool IsRawSummaryText = false) + { + public FormattableString? SummaryText => Summary.IsNullOrEmpty() ? Description : Summary; + public FormattableString? DescriptionText => Summary.IsNullOrEmpty() || Description == Summary || Description?.ToString() == Summary?.ToString() ? $"" : Description; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodSignatureModifiers.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodSignatureModifiers.cs new file mode 100644 index 0000000000..9e72e5424f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/MethodSignatureModifiers.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp +{ + [Flags] + public enum MethodSignatureModifiers + { + None = 0, + Public = 1, + Internal = 2, + Protected = 4, + Private = 8, + Static = 16, + Extension = 32, + Virtual = 64, + Async = 128, + New = 256, + Override = 512 + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Parameter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Parameter.cs new file mode 100644 index 0000000000..da8ec27c96 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Parameter.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Generator.CSharp.Input; + +namespace Microsoft.Generator.CSharp +{ + public sealed record Parameter(string Name, FormattableString? Description, CSharpType Type, Constant? DefaultValue, ValidationType Validation, FormattableString? Initializer, bool IsApiVersionParameter = false, bool IsEndpoint = false, bool IsResourceIdentifier = false, bool SkipUrlEncoding = false, RequestLocation RequestLocation = RequestLocation.None, SerializationFormat SerializationFormat = SerializationFormat.Default, bool IsPropertyBag = false, bool IsRef = false, bool IsOut = false) + { + internal bool IsRawData { get; init; } + internal static IEqualityComparer TypeAndNameEqualityComparer = new ParameterTypeAndNameEqualityComparer(); + internal CSharpAttribute[] Attributes { get; init; } = Array.Empty(); + internal bool IsOptionalInSignature => DefaultValue != null; + + internal Parameter WithRef(bool isRef = true) => IsRef == isRef ? this : this with { IsRef = isRef }; + internal Parameter ToRequired() + { + return this with { DefaultValue = null }; + } + + internal static ValidationType GetValidation(CSharpType type, RequestLocation requestLocation, bool skipUrlEncoding) + { + if (requestLocation is RequestLocation.Uri or RequestLocation.Path or RequestLocation.Body && type.Equals(typeof(string), ignoreNullable: true) && !skipUrlEncoding) + { + return ValidationType.AssertNotNullOrEmpty; + } + + if (!type.IsValueType) + { + return ValidationType.AssertNotNull; + } + + return ValidationType.None; + } + + internal static readonly IEqualityComparer EqualityComparerByType = new ParameterByTypeEqualityComparer(); + + private struct ParameterByTypeEqualityComparer : IEqualityComparer + { + public bool Equals(Parameter? x, Parameter? y) + { + return Equals(x?.Type, y?.Type); + } + + public int GetHashCode([DisallowNull] Parameter obj) => obj.Type.GetHashCode(); + } + + private class ParameterTypeAndNameEqualityComparer : IEqualityComparer + { + public bool Equals(Parameter? x, Parameter? y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (x is null || y is null) + { + return false; + } + + var result = x.Type.AreNamesEqual(y.Type) && x.Name == y.Name; + return result; + } + + public int GetHashCode([DisallowNull] Parameter obj) + { + // remove type as part of the hash code generation as the type might have changes between versions + return HashCode.Combine(obj.Name); + } + } + + // TO-DO: Migrate code from autorest as part of output classes migration : https://github.com/Azure/autorest.csharp/issues/4198 + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/PropertyBody.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/PropertyBody.cs new file mode 100644 index 0000000000..6f7a8639ad --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/PropertyBody.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp +{ + public abstract record PropertyBody; +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/PropertyDeclaration.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/PropertyDeclaration.cs new file mode 100644 index 0000000000..41678bec7f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/PropertyDeclaration.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp +{ + public record PropertyDeclaration(FormattableString? Description, MethodSignatureModifiers Modifiers, CSharpType PropertyType, CodeWriterDeclaration Declaration, PropertyBody PropertyBody, IReadOnlyDictionary? Exceptions = null, CSharpType? ExplicitInterface = null) + { + public PropertyDeclaration(FormattableString? description, MethodSignatureModifiers modifiers, CSharpType propertyType, string name, PropertyBody propertyBody, IReadOnlyDictionary? exceptions = null, CSharpType? explicitInterface = null) : this(description, modifiers, propertyType, new CodeWriterDeclaration(name), propertyBody, exceptions, explicitInterface) + { + Declaration.SetActualName(name); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/Diagnostic.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/Diagnostic.cs new file mode 100644 index 0000000000..fd3e08246a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/Diagnostic.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp +{ + public class Diagnostic + { + public Diagnostic(string scopeName, DiagnosticAttribute[]? attributes = null) + { + ScopeName = scopeName; + Attributes = attributes ?? Array.Empty(); + } + + public string ScopeName { get; } + public DiagnosticAttribute[] Attributes { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/DiagnosticAttribute.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/DiagnosticAttribute.cs new file mode 100644 index 0000000000..b012766d16 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/DiagnosticAttribute.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp +{ + public class DiagnosticAttribute + { + public DiagnosticAttribute(string name, ReferenceOrConstant value) + { + Name = name; + Value = value; + } + + public string Name { get; } + public ReferenceOrConstant Value { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/Reference.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/Reference.cs new file mode 100644 index 0000000000..84106a3ef4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/Reference.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp +{ + public readonly struct Reference + { + public Reference(string name, CSharpType type) + { + Name = name; + Type = type; + } + + public string Name { get; } + public CSharpType Type { get; } + + public static implicit operator Reference(Parameter parameter) => new Reference(parameter.Name, parameter.Type); + public static implicit operator Reference(FieldDeclaration field) => new Reference(field.Name, field.Type); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/ReferenceOrConstant.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/ReferenceOrConstant.cs new file mode 100644 index 0000000000..04e0be97e9 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Requests/ReferenceOrConstant.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp +{ + public readonly struct ReferenceOrConstant + { + private readonly Constant? _constant; + private readonly Reference? _reference; + + private ReferenceOrConstant(Constant constant) + { + Type = constant.Type; + _constant = constant; + _reference = null; + } + + private ReferenceOrConstant(Reference reference) + { + Type = reference.Type; + _reference = reference; + _constant = null; + } + + public CSharpType Type { get; } + public bool IsConstant => _constant.HasValue; + + public Constant Constant => _constant ?? throw new InvalidOperationException("Not a constant"); + public Reference Reference => _reference ?? throw new InvalidOperationException("Not a reference"); + + public static implicit operator ReferenceOrConstant(Constant constant) => new ReferenceOrConstant(constant); + public static implicit operator ReferenceOrConstant(Reference reference) => new ReferenceOrConstant(reference); + public static implicit operator ReferenceOrConstant(Parameter parameter) => new ReferenceOrConstant(new Reference(parameter.Name, parameter.Type)); + public static implicit operator ReferenceOrConstant(FieldDeclaration field) => new ReferenceOrConstant(new Reference(field.Name, field.Type)); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Serialization/SerializationFormat.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Serialization/SerializationFormat.cs new file mode 100644 index 0000000000..e38fcd8203 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Serialization/SerializationFormat.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp +{ + /// + /// Represents the set of known serialization formats. + /// + public enum SerializationFormat + { + Default, + DateTime_RFC1123, + DateTime_RFC3339, + DateTime_RFC7231, + DateTime_ISO8601, + DateTime_Unix, + Date_ISO8601, + Duration_ISO8601, + Duration_Constant, + Duration_Seconds, + Duration_Seconds_Float, + Time_ISO8601, + Bytes_Base64Url, + Bytes_Base64 + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Serialization/SerializationFormatExtensions.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Serialization/SerializationFormatExtensions.cs new file mode 100644 index 0000000000..43675dc90f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Serialization/SerializationFormatExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp +{ + internal static class SerializationFormatExtensions + { + public static string? ToFormatSpecifier(this SerializationFormat format) => format switch + { + SerializationFormat.DateTime_RFC1123 => "R", + SerializationFormat.DateTime_RFC3339 => "O", + SerializationFormat.DateTime_RFC7231 => "R", + SerializationFormat.DateTime_ISO8601 => "O", + SerializationFormat.Date_ISO8601 => "D", + SerializationFormat.DateTime_Unix => "U", + SerializationFormat.Bytes_Base64Url => "U", + SerializationFormat.Bytes_Base64 => "D", + SerializationFormat.Duration_ISO8601 => "P", + SerializationFormat.Duration_Constant => "c", + SerializationFormat.Duration_Seconds => "%s", + SerializationFormat.Duration_Seconds_Float => "s\\.fff", + SerializationFormat.Time_ISO8601 => "T", + _ => null + }; + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/TypeSignatureModifiers.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/TypeSignatureModifiers.cs new file mode 100644 index 0000000000..98dc2ea6fc --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/TypeSignatureModifiers.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp +{ + [Flags] + public enum TypeSignatureModifiers + { + None = 0, + Public = 1, + Internal = 2, + Private = 8, + Static = 16, + Partial = 32, + Sealed = 64, + Abstract = 128, + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/EnumType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/EnumType.cs new file mode 100644 index 0000000000..21d8a3458a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/EnumType.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.Generator.CSharp.Input; + +namespace Microsoft.Generator.CSharp +{ + public class EnumType : TypeProvider + { + private readonly IEnumerable _allowedValues; + private readonly ModelTypeMapping? _typeMapping; + private readonly TypeFactory _typeFactory; + + public EnumType(InputEnumType input, string defaultNamespace, TypeFactory typeFactory, SourceInputModel? sourceInputModel) + : base(sourceInputModel) + { + _allowedValues = input.AllowedValues; + _typeFactory = typeFactory; + _deprecated = input.Deprecated; + + if (input.Accessibility == "internal") + { + DeclarationModifiers = TypeSignatureModifiers.Internal; + } else + { + DeclarationModifiers = TypeSignatureModifiers.Public; + } + IsAccessibilityOverridden = input.Accessibility != null; + + var isExtensible = input.IsExtensible; + if (ExistingType != null) + { + isExtensible = ExistingType.TypeKind switch + { + TypeKind.Enum => false, + TypeKind.Struct => true, + _ => throw new InvalidOperationException( + $"{ExistingType.ToDisplayString()} cannot be mapped to enum," + + $" expected enum or struct got {ExistingType.TypeKind}") + }; + + _typeMapping = sourceInputModel?.CreateForModel(ExistingType); + } + + Name = input.Name.ToCleanName(); + IsExtensible = isExtensible; + ValueType = typeFactory.CreateType(input.EnumValueType); + IsStringValueType = ValueType.Equals(typeof(string)); + IsIntValueType = ValueType.Equals(typeof(int)) || ValueType.Equals(typeof(long)); + IsFloatValueType = ValueType.Equals(typeof(float)) || ValueType.Equals(typeof(double)); + IsNumericValueType = IsIntValueType || IsFloatValueType; + SerializationMethodName = IsStringValueType && IsExtensible ? "ToString" : $"ToSerial{ValueType.Name.FirstCharToUpperCase()}"; + + Description = input.Description; + } + + public CSharpType ValueType { get; } + public bool IsExtensible { get; } + public bool IsIntValueType { get; } + public bool IsFloatValueType { get; } + public bool IsStringValueType { get; } + public bool IsNumericValueType { get; } + public string SerializationMethodName { get; } + public string? Description { get; } + public override string Name { get; } + protected override TypeKind TypeKind => IsExtensible ? TypeKind.Struct : TypeKind.Enum; + public bool IsAccessibilityOverridden { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/ModelTypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/ModelTypeProvider.cs new file mode 100644 index 0000000000..fefd6fd566 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/ModelTypeProvider.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Input; + +namespace Microsoft.Generator.CSharp +{ + public sealed class ModelTypeProvider : TypeProvider + { + private readonly InputModelType _inputModel; + + public override string Name { get; } + + public ModelTypeProvider(InputModelType inputModel, SourceInputModel? sourceInputModel) + : base(sourceInputModel) + { + Name = inputModel.Name.ToCleanName(); + + if (inputModel.Accessibility == "internal") + { + DeclarationModifiers = TypeSignatureModifiers.Partial | TypeSignatureModifiers.Internal; + } + + bool isAbstract = inputModel.DiscriminatorPropertyName is not null && inputModel.DiscriminatorValue is null; + if (isAbstract) + { + DeclarationModifiers |= TypeSignatureModifiers.Abstract; + } + + _inputModel = inputModel; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/OutputLibrary.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/OutputLibrary.cs new file mode 100644 index 0000000000..3f0eb90b73 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/OutputLibrary.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Input; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp +{ + public abstract class OutputLibrary + { + protected InputNamespace Input { get; } + + public OutputLibrary(InputNamespace input) + { + Input = input; + } + + private IReadOnlyList? _models; + + public IReadOnlyList Models => _models ??= BuildModels(); + + protected abstract ModelTypeProvider[] BuildModels(); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/TypeProvider.cs new file mode 100644 index 0000000000..a7922b65f9 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Models/Types/TypeProvider.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.CodeAnalysis; +using Microsoft.Generator.CSharp.Expressions; +using System; +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp +{ + public abstract class TypeProvider + { + private readonly Lazy _existingType; + + protected readonly SourceInputModel? _sourceInputModel; + protected string? _deprecated; + + protected TypeProvider(SourceInputModel? sourceInputModel) + { + _sourceInputModel = sourceInputModel; + _existingType = new Lazy(() => sourceInputModel?.FindForType(Name)); + DeclarationModifiers = TypeSignatureModifiers.Partial | TypeSignatureModifiers.Public; + } + + public abstract string Name { get; } + protected virtual TypeKind TypeKind { get; } = TypeKind.Class; + protected INamedTypeSymbol? ExistingType => _existingType.Value; + + internal virtual Type? SerializeAs => null; + + public string? Deprecated => _deprecated; + + private CSharpType? _type; + public CSharpType Type => _type ??= new( + this, + isValueType: TypeKind is TypeKind.Struct or TypeKind.Enum, + isEnum: this is EnumType, + isNullable: false, + arguments: TypeArguments, + ns: GetDefaultModelNamespace(CodeModelPlugin.Instance.Configuration.Namespace), + name: Name); + + public TypeSignatureModifiers DeclarationModifiers { get; protected init; } + + public CSharpType? Inherits { get; protected init; } + + public virtual WhereExpression? WhereClause { get; protected init; } + + public bool IsEnum => TypeKind is TypeKind.Enum; + + public bool IsStruct => TypeKind is TypeKind.Struct; + + private CSharpType[]? _typeArguments; + public virtual IReadOnlyList TypeArguments => _typeArguments ??= BuildTypeArguments(); + + public virtual TypeProvider? DeclaringTypeProvider { get; protected init; } + + private IReadOnlyList? _implements; + public IReadOnlyList Implements => _implements ??= BuildImplements(); + + private IReadOnlyList? _properties; + public IReadOnlyList Properties => _properties ??= BuildProperties(); + + private IReadOnlyList? _methods; + public IReadOnlyList Methods => _methods ??= BuildMethods(); + + private IReadOnlyList? _constructors; + public IReadOnlyList Constructors => _constructors ??= BuildConstructors(); + + private IReadOnlyList? _fields; + public IReadOnlyList Fields => _fields ??= BuildFields(); + + private IReadOnlyList? _nestedTypes; + public IReadOnlyList NestedTypes => _nestedTypes ??= BuildNestedTypes(); + + protected virtual CSharpType[] BuildTypeArguments() => Array.Empty(); + + protected virtual PropertyDeclaration[] BuildProperties() => Array.Empty(); + + protected virtual FieldDeclaration[] BuildFields() => Array.Empty(); + + protected virtual CSharpType[] BuildImplements() => Array.Empty(); + + protected virtual Method[] BuildMethods() => Array.Empty(); + + protected virtual Method[] BuildConstructors() => Array.Empty(); + + protected virtual TypeProvider[] BuildNestedTypes() => Array.Empty(); + + public static string GetDefaultModelNamespace(string defaultNamespace) + { + if (CodeModelPlugin.Instance.Configuration.UseModelNamespace) + { + return $"{defaultNamespace}.Models"; + } + + return defaultNamespace; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Properties/AssemblyInfo.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..ee3066513e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Generator.CSharp.Tests")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Properties/launchSettings.json b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Properties/launchSettings.json new file mode 100644 index 0000000000..12116140cc --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Unbranded-TypeSpec": { + "commandName": "Project", + "commandLineArgs": "$(SolutionDir)\\Microsoft.Generator.CSharp.ClientModel.TestProjects\\Unbranded-TypeSpec" + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/SA1505Rewriter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/SA1505Rewriter.cs new file mode 100644 index 0000000000..b51de9391e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/SA1505Rewriter.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Generator.CSharp +{ + /// + /// This Roslyn CSharpSyntaxRewriter will delete extra newlines after the + /// open brace of a class/struct/enum definition. It is for resolving SA1505 error. + /// + internal class SA1505Rewriter : CSharpSyntaxRewriter + { + public override SyntaxNode? VisitClassDeclaration(ClassDeclarationSyntax node) + => base.VisitClassDeclaration((ClassDeclarationSyntax)RemoveEOLAfterOpenBrace(node)); + + public override SyntaxNode? VisitStructDeclaration(StructDeclarationSyntax node) + => base.VisitStructDeclaration((StructDeclarationSyntax)RemoveEOLAfterOpenBrace(node)); + + public override SyntaxNode? VisitEnumDeclaration(EnumDeclarationSyntax node) + => base.VisitEnumDeclaration((EnumDeclarationSyntax)RemoveEOLAfterOpenBrace(node)); + + private BaseTypeDeclarationSyntax RemoveEOLAfterOpenBrace(BaseTypeDeclarationSyntax node) + { + // all extra EOL after open brace are the leading trivia of the next token + var nextToken = node.OpenBraceToken.GetNextToken(); + if (nextToken.IsMissing) + { + return node; + } + + var leadingTrivia = nextToken.LeadingTrivia; + if (leadingTrivia.Count == 0 || !leadingTrivia[0].IsKind(SyntaxKind.EndOfLineTrivia)) + { + return node; + } + + var newLeadingTrivia = leadingTrivia.SkipWhile(t => t.IsKind(SyntaxKind.EndOfLineTrivia)); + var newNextToken = nextToken.WithLeadingTrivia(newLeadingTrivia); + return node.ReplaceToken(nextToken, newNextToken); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/ClientSourceInput.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/ClientSourceInput.cs new file mode 100644 index 0000000000..93750e5342 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/ClientSourceInput.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.CodeAnalysis; + +namespace Microsoft.Generator.CSharp.Input +{ + internal record ClientSourceInput(INamedTypeSymbol? ParentClientType); +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/CodeGenAttributes.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/CodeGenAttributes.cs new file mode 100644 index 0000000000..50d189308c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/CodeGenAttributes.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.Generator.CSharp.Customization; + +namespace Microsoft.Generator.CSharp +{ + internal class CodeGenAttributes + { + public const string CodeGenSuppressAttributeName = "CodeGenSuppressAttribute"; + + public const string CodeGenMemberAttributeName = "CodeGenMemberAttribute"; + + public const string CodeGenTypeAttributeName = "CodeGenTypeAttribute"; + + public const string CodeGenModelAttributeName = "CodeGenModelAttribute"; + + public const string CodeGenClientAttributeName = "CodeGenClientAttribute"; + + public const string CodeGenSerializationAttributeName = "CodeGenSerializationAttribute"; + + public bool TryGetCodeGenMemberAttributeValue(AttributeData attributeData, [MaybeNullWhen(false)] out string name) + { + name = null; + if (attributeData.AttributeClass?.Name != CodeGenMemberAttributeName) + return false; + + name = attributeData.ConstructorArguments.FirstOrDefault().Value as string; + return name != null; + } + + public bool TryGetCodeGenSerializationAttributeValue(AttributeData attributeData, [MaybeNullWhen(false)] out string propertyName, out IReadOnlyList? serializationNames, out string? serializationHook, out string? deserializationHook, out string? bicepSerializationHook) + { + propertyName = null; + serializationNames = null; + serializationHook = null; + deserializationHook = null; + bicepSerializationHook = null; + if (attributeData.AttributeClass?.Name != CodeGenSerializationAttributeName) + { + return false; + } + + var ctorArgs = attributeData.ConstructorArguments; + // this attribute could only at most have one constructor + propertyName = ctorArgs[0].Value as string; + + if (ctorArgs.Length > 1) + { + var namesArg = ctorArgs[1]; + serializationNames = namesArg.Kind switch + { + TypedConstantKind.Array => ToStringArray(namesArg.Values), + _ when namesArg.IsNull => null, + _ => new string[] { namesArg.Value?.ToString()! } + }; + } + + foreach (var (key, namedArgument) in attributeData.NamedArguments) + { + switch (key) + { + case nameof(CodeGenSerializationAttribute.SerializationPath): + serializationNames = ToStringArray(namedArgument.Values); + break; + case nameof(CodeGenSerializationAttribute.SerializationValueHook): + serializationHook = namedArgument.Value as string; + break; + case nameof(CodeGenSerializationAttribute.DeserializationValueHook): + deserializationHook = namedArgument.Value as string; + break; + } + } + + return propertyName != null && (serializationNames != null || serializationHook != null || deserializationHook != null || bicepSerializationHook != null); + } + + public bool TryGetCodeGenModelAttributeValue(AttributeData attributeData, out string[]? usage, out string[]? formats) + { + usage = null; + formats = null; + if (attributeData.AttributeClass?.Name != CodeGenModelAttributeName) + return false; + foreach (var namedArgument in attributeData.NamedArguments) + { + switch (namedArgument.Key) + { + case nameof(CodeGenModelAttribute.Usage): + usage = ToStringArray(namedArgument.Value.Values); + break; + case nameof(CodeGenModelAttribute.Formats): + formats = ToStringArray(namedArgument.Value.Values); + break; + } + } + + return usage != null || formats != null; + } + + private static string[]? ToStringArray(ImmutableArray values) + { + if (values.IsDefaultOrEmpty) + { + return null; + } + + return values + .Select(v => (string?)v.Value) + .OfType() + .ToArray(); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/CompilationCustomCode.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/CompilationCustomCode.cs new file mode 100644 index 0000000000..3b4b87e718 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/CompilationCustomCode.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Generator.CSharp +{ + public abstract class CompilationCustomCode + { + protected Compilation _compilation; + + public CompilationCustomCode(Compilation compilation) + { + _compilation = compilation; + } + + internal abstract IMethodSymbol? FindMethod(string namespaceName, string typeName, string methodName, IEnumerable parameters); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/ModelTypeMapping.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/ModelTypeMapping.cs new file mode 100644 index 0000000000..5f0e1a7cc4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/ModelTypeMapping.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Generator.CSharp +{ + internal class ModelTypeMapping + { + private readonly Dictionary _propertyMappings; + private readonly Dictionary _codeGenMemberMappings; + private readonly Dictionary _typeSerializationMappings; + + public string[]? Usages { get; } + public string[]? Formats { get; } + + public ModelTypeMapping(CodeGenAttributes codeGenAttributes, INamedTypeSymbol existingType) + { + _propertyMappings = new(); + _codeGenMemberMappings = new(); + _typeSerializationMappings = new(); + + foreach (ISymbol member in GetMembers(existingType)) + { + // If member is defined in both base and derived class, use derived one + if (ShouldIncludeMember(member) && !_propertyMappings.ContainsKey(member.Name)) + { + _propertyMappings[member.Name] = member; + } + + foreach (var attributeData in member.GetAttributes()) + { + // handle CodeGenMember attribute + if (codeGenAttributes.TryGetCodeGenMemberAttributeValue(attributeData, out var schemaMemberName)) + { + _codeGenMemberMappings[schemaMemberName] = member; + } + } + } + + foreach (var attributeData in existingType.GetAttributes()) + { + // handle CodeGenModel attribute + if (codeGenAttributes.TryGetCodeGenModelAttributeValue(attributeData, out var usage, out var formats)) + { + Usages = usage; + Formats = formats; + } + + // handle CodeGenSerialization attribute + if (codeGenAttributes.TryGetCodeGenSerializationAttributeValue(attributeData, out var propertyName, out var serializationNames, out var serializationHook, out var deserializationHook, out var bicepSerializationHook) && !_typeSerializationMappings.ContainsKey(propertyName)) + { + _typeSerializationMappings.Add(propertyName, new(propertyName, serializationNames, serializationHook, deserializationHook, bicepSerializationHook)); + } + } + } + + private static bool ShouldIncludeMember(ISymbol member) + { + // here we exclude those "CompilerGenerated" members, such as the backing field of a property which is also a field. + return !member.IsImplicitlyDeclared && member is IPropertySymbol or IFieldSymbol; + } + + public ISymbol? GetMemberByOriginalName(string name) + => _codeGenMemberMappings.TryGetValue(name, out var renamedSymbol) ? + renamedSymbol : + _propertyMappings.TryGetValue(name, out var memberSymbol) ? memberSymbol : null; + + public SourcePropertySerializationMapping? GetForMemberSerialization(string name) + => _typeSerializationMappings.TryGetValue(name, out var serialization) ? serialization : null; + + public IEnumerable GetPropertiesWithSerialization() + { + // only the property with CodeGenSerialization attribute will be emitted into the serialization code. + foreach (var (propertyName, symbol) in _propertyMappings) + { + if (_typeSerializationMappings.ContainsKey(propertyName)) + yield return symbol; + } + } + + private static IEnumerable GetMembers(INamedTypeSymbol? typeSymbol) + { + while (typeSymbol != null) + { + foreach (var symbol in typeSymbol.GetMembers()) + { + yield return symbol; + } + + typeSymbol = typeSymbol.BaseType; + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/ProtocolCompilationCustomCode.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/ProtocolCompilationCustomCode.cs new file mode 100644 index 0000000000..2d225dc687 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/ProtocolCompilationCustomCode.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Generator.CSharp +{ + internal class ProtocolCompilationCustomCode : CompilationCustomCode + { + private List? _methodSet; + private List MethodSet => _methodSet ??= EnsureMethodSet(); + + public ProtocolCompilationCustomCode(Compilation compilation) + : base(compilation) { } + + private protected List EnsureMethodSet() + { + var result = new List(); + foreach (IModuleSymbol module in _compilation.Assembly.Modules) + { + foreach (var type in SourceInputHelper.GetSymbols(module.GlobalNamespace)) + { + if (type is INamedTypeSymbol typeSymbol && IsClient(typeSymbol)) + { + foreach (var member in typeSymbol.GetMembers()) + { + if (member is IMethodSymbol methodSymbol && IsProtocolMethod(methodSymbol)) + { + result.Add(methodSymbol); + } + } + } + } + } + return result; + } + + internal override IMethodSymbol? FindMethod(string namespaceName, string typeName, string methodName, IEnumerable parameters) + { + var methods = MethodSet.Where(m => + m.ContainingNamespace.ToString() == namespaceName && + m.ContainingType.Name == typeName && + m.Name == methodName).ToArray(); + if (methods.Length == 0) + { + return null; + } + else if (methods.Length == 1) + { + return methods.First(); + } + else + { + foreach (var method in methods) + { + var existingParameters = method.Parameters; + var parametersCount = parameters.Count(); + if (existingParameters.Length - 1 != parametersCount) + { + continue; + } + + int index = 0; + foreach (var parameter in parameters) + { + if ((existingParameters[index].Type as INamedTypeSymbol)!.IsSameType(parameter)) + { + break; + } + ++index; + } + + if (index == parametersCount) + { + return method; + } + } + return null; + } + } + + private bool IsClient(INamedTypeSymbol type) => type.Name.EndsWith("Client"); + private bool IsProtocolMethod(IMethodSymbol method) => method.Parameters.Length > 0 && (method.Parameters.Last().Type as INamedTypeSymbol)!.IsSameType(KnownParameters.RequestContext.Type); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/SourceInputHelper.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/SourceInputHelper.cs new file mode 100644 index 0000000000..4750b226ff --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/SourceInputHelper.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Generator.CSharp +{ + internal static class SourceInputHelper + { + internal static IEnumerable GetSymbols(INamespaceSymbol namespaceSymbol) + { + foreach (var childNamespaceSymbol in namespaceSymbol.GetNamespaceMembers()) + { + foreach (var symbol in GetSymbols(childNamespaceSymbol)) + { + yield return symbol; + } + } + + foreach (INamedTypeSymbol symbol in namespaceSymbol.GetTypeMembers()) + { + yield return symbol; + } + } + + internal static bool TryGetExistingMethod(INamedTypeSymbol? type, MethodSignature signature, [MaybeNullWhen(false)] out IMethodSymbol method) + { + if (type is null) + { + method = null; + return false; + } + + foreach (var member in type.GetMembers()) + { + if (member.Name != signature.Name) + { + continue; + } + + if (member is not IMethodSymbol candidate) + { + continue; + } + + if (signature.ExplicitInterface is { }) + { + if (candidate.MethodKind != MethodKind.ExplicitInterfaceImplementation) + { + continue; + } + } + else + { + if (candidate.MethodKind != MethodKind.Ordinary) + { + continue; + } + } + + if (TypesAreEqual(candidate.Parameters, signature.Parameters)) + { + method = candidate; + return true; + } + } + + method = null; + return false; + } + + private static bool TypesAreEqual(ImmutableArray candidateParameters, IReadOnlyList signatureParameters) + { + if (candidateParameters.Length != signatureParameters.Count) + { + return false; + } + + for (var i = 0; i < candidateParameters.Length; i++) + { + var candidateParameterType = candidateParameters[i].Type; + var signatureParameterType = signatureParameters[i].Type; + if (candidateParameterType is not INamedTypeSymbol typeSymbol || !typeSymbol.IsSameType(signatureParameterType)) + { + return false; + } + } + + return true; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/SourceInputModel.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/SourceInputModel.cs new file mode 100644 index 0000000000..6537541a41 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/SourceInputModel.cs @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Build.Construction; +using Microsoft.CodeAnalysis; +using Microsoft.Generator.CSharp.Customization; +using Microsoft.Generator.CSharp.Input; +using NuGet.Configuration; + +namespace Microsoft.Generator.CSharp +{ + public sealed class SourceInputModel + { + private readonly CompilationCustomCode? _existingCompilation; + private readonly CodeGenAttributes _codeGenAttributes; + private readonly Dictionary _nameMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public Compilation Customization { get; } + public Compilation? PreviousContract { get; } + + public SourceInputModel(Compilation customization, CompilationCustomCode? existingCompilation = null) + { + Customization = customization; + PreviousContract = LoadBaselineContract().GetAwaiter().GetResult(); + _existingCompilation = existingCompilation; + + _codeGenAttributes = new CodeGenAttributes(); + + IAssemblySymbol assembly = Customization.Assembly; + + foreach (IModuleSymbol module in assembly.Modules) + { + foreach (var type in SourceInputHelper.GetSymbols(module.GlobalNamespace)) + { + if (type is INamedTypeSymbol namedTypeSymbol && TryGetName(type, out var schemaName)) + { + _nameMap.Add(schemaName, namedTypeSymbol); + } + } + } + } + + public IReadOnlyList? GetServiceVersionOverrides() + { + var osvAttributeType = Customization.GetTypeByMetadataName(typeof(CodeGenOverrideServiceVersionsAttribute).FullName!)!; + var osvAttribute = Customization.Assembly.GetAttributes() + .FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, osvAttributeType)); + + return osvAttribute?.ConstructorArguments[0].Values.Select(v => v.Value).OfType().ToList(); + } + + internal ModelTypeMapping? CreateForModel(INamedTypeSymbol? symbol) + { + if (symbol == null) + return null; + + return new ModelTypeMapping(_codeGenAttributes, symbol); + } + + internal IMethodSymbol? FindMethod(string namespaceName, string typeName, string methodName, IEnumerable parameters) + { + return _existingCompilation?.FindMethod(namespaceName, typeName, methodName, parameters); + } + + public INamedTypeSymbol? FindForType(string name) + { + var ns = TypeProvider.GetDefaultModelNamespace(CodeModelPlugin.Instance.Configuration.Namespace); + var fullyQualifiedMetadataName = $"{ns}.{name}"; + if (!_nameMap.TryGetValue(name, out var type) && + !_nameMap.TryGetValue(fullyQualifiedMetadataName, out type)) + { + type = Customization.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName); + } + + return type; + } + + internal bool TryGetClientSourceInput(INamedTypeSymbol type, [NotNullWhen(true)] out ClientSourceInput? clientSourceInput) + { + foreach (var attribute in type.GetAttributes()) + { + var attributeType = attribute.AttributeClass; + while (attributeType != null) + { + if (attributeType.Name == CodeGenAttributes.CodeGenClientAttributeName) + { + INamedTypeSymbol? parentClientType = null; + foreach ((var argumentName, TypedConstant constant) in attribute.NamedArguments) + { + if (argumentName == nameof(CodeGenClientAttribute.ParentClient)) + { + parentClientType = (INamedTypeSymbol?)constant.Value; + } + } + + clientSourceInput = new ClientSourceInput(parentClientType); + return true; + } + + attributeType = attributeType.BaseType; + } + } + + clientSourceInput = null; + return false; + } + + private bool TryGetName(ISymbol symbol, [NotNullWhen(true)] out string? name) + { + name = null; + + foreach (var attribute in symbol.GetAttributes()) + { + INamedTypeSymbol? type = attribute.AttributeClass; + while (type != null) + { + if (type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat) == CodeGenAttributes.CodeGenTypeAttributeName) + { + if (attribute?.ConstructorArguments.Length > 0) + { + name = attribute.ConstructorArguments[0].Value as string; + break; + } + } + + type = type.BaseType; + } + } + + return name != null; + } + + private async Task LoadBaselineContract() + { + + string fullPath; + string projectFilePath = Path.GetFullPath(Path.Combine(CodeModelPlugin.Instance.Configuration.ProjectDirectory, $"{CodeModelPlugin.Instance.Configuration.Namespace}.csproj")); + if (!File.Exists(projectFilePath)) + return null; + + var baselineVersion = ProjectRootElement.Open(projectFilePath).Properties.SingleOrDefault(p => p.Name == "ApiCompatVersion")?.Value; + + if (baselineVersion is not null) + { + var nugetGlobalPackageFolder = SettingsUtility.GetGlobalPackagesFolder(new NullSettings()); + var nugetFolder = Path.Combine(nugetGlobalPackageFolder, CodeModelPlugin.Instance.Configuration.Namespace.ToLowerInvariant(), baselineVersion, "lib", "netstandard2.0"); + fullPath = Path.Combine(nugetFolder, $"{CodeModelPlugin.Instance.Configuration.Namespace}.dll"); + if (File.Exists(fullPath)) + { + return await GeneratedCodeWorkspace.CreatePreviousContractFromDll(Path.Combine(nugetFolder, $"{CodeModelPlugin.Instance.Configuration.Namespace}.xml"), fullPath); + } + else + { + throw new InvalidOperationException($"Can't find Baseline contract assembly ({CodeModelPlugin.Instance.Configuration.Namespace}@{baselineVersion}) from Nuget Global Package Folder at {fullPath}. " + + $"Please make sure the baseline nuget package has been installed properly"); + } + } + return null; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/SourcePropertySerializationMapping.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/SourcePropertySerializationMapping.cs new file mode 100644 index 0000000000..e4ba41fddf --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Source/SourcePropertySerializationMapping.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp +{ + public sealed class SourcePropertySerializationMapping + { + public SourcePropertySerializationMapping(string propertyName, IReadOnlyList? serializationPath, string? jsonSerializationValueHook, string? jsonDeserializationValueHook, string? bicepSerializationValueHook) + { + PropertyName = propertyName; + SerializationPath = serializationPath; + JsonSerializationValueHook = jsonSerializationValueHook; + JsonDeserializationValueHook = jsonDeserializationValueHook; + BicepSerializationValueHook = bicepSerializationValueHook; + } + + internal string PropertyName { get; } + internal IReadOnlyList? SerializationPath { get; } + internal string? JsonSerializationValueHook { get; } + internal string? JsonDeserializationValueHook { get; } + + internal string? BicepSerializationValueHook { get; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/CommandLineOptions.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/CommandLineOptions.cs new file mode 100644 index 0000000000..781ee31cb4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/CommandLineOptions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using System.IO; +using CommandLine; + +namespace Microsoft.Generator.CSharp +{ + /// + /// Options for the command line when running the generator. + /// + internal class CommandLineOptions + { + private const string OutputDirectoryOptionName = "DIRECTORY"; + private const string ModelPluginOptionName = "model-plugin"; + private const string ShouldDebugOptionName = "debug"; + private const string CmdLineOutputDirectoryOptionHelpText = "The path to the directory containing the input files to the generator including the code model file and the configuration file for the generator."; + private const string CmdLineModelPluginOptionHelpText = "The name of the custom client model plugin NuGet package to use to generate the code. If not provided, the default client model plugin package `Microsoft.Generator.CSharp.ClientModel` will be used."; + private const string CmdLineDebugOptionHelpText = "Attempt to attach the debugger on execute."; + + /// + /// The command line option to specify the path to the directory containing the input files to the generator. + /// + [Value(0, MetaName = OutputDirectoryOptionName, Default = null, Required = true, HelpText = CmdLineOutputDirectoryOptionHelpText)] + [NotNull] + public string? OutputDirectory { get; set; } + + /// + /// The command line option to specify the name of the custom client model plugin Nuget package to use to generate the client. + /// + [Option(ModelPluginOptionName, Required = false, Default = null, HelpText = CmdLineModelPluginOptionHelpText)] + public string? ModelPlugin { get; set; } + + [Option(longName: ShouldDebugOptionName, Required = false, Default = false, Hidden = true, HelpText = CmdLineDebugOptionHelpText)] + public bool ShouldDebug { get; set; } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/GeneratorRunner.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/GeneratorRunner.cs new file mode 100644 index 0000000000..2ac3a7d30c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/GeneratorRunner.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; + +namespace Microsoft.Generator.CSharp +{ + internal class GeneratorRunner + { + public async Task RunAsync(CommandLineOptions options) + { + PluginHandler pluginHandler = new(options.ModelPlugin); + pluginHandler.LoadPlugin(options.OutputDirectory); + + var csharpGen = new CSharpGen(); + await csharpGen.ExecuteAsync(); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/PluginHandler.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/PluginHandler.cs new file mode 100644 index 0000000000..2fe8864c15 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/PluginHandler.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using System.IO; + +namespace Microsoft.Generator.CSharp +{ + internal class PluginHandler + { + private readonly string _defaultClientModelPluginId = "@typespec" + Path.DirectorySeparatorChar + "http-client-csharp-generator-clientModel"; + private readonly string _pluginName; + + /// + /// Constructs a new instance of with the specified plugin name and version. + /// The plugin is then loaded using the method. + /// + /// The name of the plugin to load. + public PluginHandler(string? name) + { + _pluginName = name ?? _defaultClientModelPluginId; + } + + private string? _nodeModulesFolder; + private string NodeModulesFolder => _nodeModulesFolder ??= GetDirectoryCatalog(); + + private string GetDirectoryCatalog() + { + return FindNodeModules(new DirectoryInfo(AppContext.BaseDirectory)); + } + + public void LoadPlugin(string outputDirectory) + { + using DirectoryCatalog directoryCatalog = new(Path.Combine(NodeModulesFolder, _pluginName)); + using (CompositionContainer container = new(directoryCatalog)) + { + try + { + container.ComposeExportedValue(new GeneratorContext(Configuration.Load(outputDirectory))); + var plugin = container.GetExportedValue(); + if (plugin == null) + { + throw new InvalidOperationException($"Cannot find exported value in composition container for {_pluginName}."); + } + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to load client model plugin {_pluginName}.", ex); + } + } + } + + private string FindNodeModules(DirectoryInfo? path) + { + if (path is null) + { + throw new InvalidOperationException($"Unable to find node_modules in path {AppContext.BaseDirectory}"); + } + + if (path.Name == "node_modules") + { + return path.FullName; + } + + foreach (var child in path.GetDirectories()) + { + if (child.Name == "node_modules") + { + return child.FullName; + } + } + + return FindNodeModules(path.Parent); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/Program.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/Program.cs new file mode 100644 index 0000000000..0447fc9b63 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/StartUp/Program.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using CommandLine; + +namespace Microsoft.Generator.CSharp +{ + internal static class Program + { + public static async Task Main(string[] args) + { + GeneratorRunner runner = new GeneratorRunner(); + + // If no arguments are passed, show help message + if (args.Length == 0) + { + args = new[] { "--help" }; + } + + return await Parser.Default.ParseArguments(args) + .MapResult(async (CommandLineOptions opts) => + { + return await Run(opts, runner); + }, + errs => Task.FromResult(-1)); + } + + private static async Task Run(CommandLineOptions options, GeneratorRunner runner) + { + if (options.ShouldDebug) + { + await Console.Error.WriteLineAsync("Attempting to attach debugger.."); + Debugger.Launch(); + } + + try + { + await runner.RunAsync(options); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error: {ex.Message}"); + return 1; + } + + Console.Error.WriteLine("Shutting Down"); + return 0; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/TypeFactory.cs new file mode 100644 index 0000000000..54e9947717 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/TypeFactory.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Input; + +namespace Microsoft.Generator.CSharp +{ + /// + /// This class provides a method for creating CSharpType objects based on input types. + /// + public abstract class TypeFactory + { + /// + /// Factory method for creating a based on an input type . + /// + /// The to convert. + /// An instance of . + public abstract CSharpType CreateType(InputType input); + + /// + /// Factory method for creating a based on an input operation . + /// + /// The to convert. + /// Flag that can be used to determine if the protocol method should be returned. + /// The constructed . + public abstract Method CreateMethod(InputOperation operation, bool returnProtocol = true); + public abstract CSharpType MatchConditionsType(); + public abstract CSharpType RequestConditionsType(); + public abstract CSharpType TokenCredentialType(); + public abstract CSharpType PageResponseType(); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/Disposable.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/Disposable.cs new file mode 100644 index 0000000000..33bb2ccea5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/Disposable.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; + +namespace Microsoft.Generator.CSharp +{ + /// + /// Provides a set of static methods for creating Disposables. + /// + internal static class Disposable + { + /// + /// Creates a disposable object that invokes the specified action when disposed. + /// + /// Action to run during the first call to . The action is guaranteed to be run at most once. + /// The disposable object that runs the given action upon disposal. + /// is null. + public static IDisposable Create(Action dispose) + { + ArgumentNullException.ThrowIfNull(dispose, nameof(dispose)); + return new AnonymousDisposable(dispose); + } + + /// + /// Creates a disposable wrapper for a disposable that will be invoked at most once. + /// + /// Disposable that will be wrapped. Disposable is guaranteed to be run at most once. + /// The disposable object that disposes wrapped object. + /// is null. + public static IDisposable Create(IDisposable disposable) + { + ArgumentNullException.ThrowIfNull(disposable, nameof(disposable)); + return new DisposableWrapper(disposable); + } + + /// + /// Gets the disposable that does nothing when disposed. + /// + public static IDisposable Empty { get; } = new AnonymousDisposable(null); + + /// + /// Represents an Action-based disposable. + /// + private sealed class AnonymousDisposable : IDisposable + { + private Action? _dispose; + + /// + /// Constructs a new disposable with the given action used for disposal. + /// + /// Disposal action which will be run upon calling Dispose. + public AnonymousDisposable(Action? dispose) + { + _dispose = dispose; + } + + /// + /// Calls the disposal action if and only if the current instance hasn't been disposed yet. + /// + public void Dispose() + { + var action = Interlocked.Exchange(ref _dispose, null); + action?.Invoke(); + } + } + + /// + /// Represents a disposable wrapper. + /// + private sealed class DisposableWrapper : IDisposable + { + private IDisposable? _disposable; + + public DisposableWrapper(IDisposable disposable) + { + _disposable = disposable; + } + + /// + /// Disposes wrapped object if and only if the current instance hasn't been disposed yet. + /// + public void Dispose() + { + var disposable = Interlocked.Exchange(ref _disposable, null); + disposable?.Dispose(); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/FormattableStringHelpers.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/FormattableStringHelpers.cs new file mode 100644 index 0000000000..98c8741a8c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/FormattableStringHelpers.cs @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Security.AccessControl; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Generator.CSharp +{ + internal static class FormattableStringHelpers + { + private static FormattableString Join(IEnumerable source, int count, Func converter, string separator, string? lastSeparator, char? format) + => count switch + { + 0 => Empty, + 1 => FormattableStringFactory.Create(format is not null ? $"{{0:{format}}}" : "{0}", converter(source.First())), + _ => FormattableStringFactory.Create(CreateFormatWithSeparator(separator, lastSeparator, format, count), source.Select(converter).ToArray()) + }; + private static string CreateFormatWithSeparator(string separator, string? lastSeparator, char? format, int count) + { + const int offset = 48; // (int)'0' is 48 + if (count > 100) + { + var s = string.Join(separator, Enumerable.Range(0, count).Select(i => $"{{{i}}}")); + return lastSeparator is null ? s : s.ReplaceLast(separator, lastSeparator); + } + + Debug.Assert(count > 1); + + lastSeparator ??= separator; + + var placeholderLength = format.HasValue ? 5 : 3; + var length = count < 10 + ? count * placeholderLength + : (count - 10) * (placeholderLength + 1) + 10 * placeholderLength; + + length += separator.Length * (count - 2) + lastSeparator.Length; + + return string.Create(length, (separator, lastSeparator, format, count), static (span, state) => + { + var (separator, lastSeparator, format, count) = state; + for (int i = 0; i < count; i++) + { + span[0] = '{'; + if (i < 10) + { + span[1] = (char)(i + offset); + span = span[2..]; + } + else + { + span[1] = (char)(i / 10 + offset); + span[2] = (char)(i % 10 + offset); + span = span[3..]; + } + if (format is not null) + { + span[0] = ':'; + span[1] = format.Value; + span = span[2..]; + } + span[0] = '}'; + span = span[1..]; + if (i < count - 1) + { + var separatorToUse = i < count - 2 ? separator : lastSeparator; + separatorToUse.CopyTo(span); + span = span[separatorToUse.Length..]; + } + } + Debug.Assert(span.IsEmpty); + }); + } + public static FormattableString Empty => $""; + + [return: NotNullIfNotNull(nameof(s))] + public static FormattableString? FromString(string? s) => + s is null ? null : s.Length == 0 ? Empty : $"{s}"; + public static bool IsNullOrEmpty(this FormattableString? fs) => + fs is null || string.IsNullOrEmpty(fs.Format) && fs.ArgumentCount == 0; + + public static FormattableString GetTypesFormattable(this IReadOnlyCollection parameters) + => GetTypesFormattable(parameters, parameters.Count); + + public static FormattableString GetTypesFormattable(this IEnumerable parameters, int count) + => Join(parameters, count, static p => p.Type, ",", null, null); + + public static string ReplaceLast(this string text, string oldValue, string newValue) + { + var position = text.LastIndexOf(oldValue, StringComparison.Ordinal); + return position < 0 ? text : text.Substring(0, position) + newValue + text.Substring(position + oldValue.Length); + } + + public static FormattableString GetReferenceFormattable(this Reference reference) + { + var parts = reference.Name.Split(".").ToArray(); + return Join(parts, parts.Length, static s => s, ".", null, 'I'); + } + + public static FormattableString GetReferenceOrConstantFormattable(this ReferenceOrConstant value) + => value.IsConstant ? value.Constant.GetConstantFormattable() : value.Reference.GetReferenceFormattable(); + + /// + /// This method parses the into a . + /// + /// The to parse. + /// Flag used to determine if the constant should be written as a string. + /// The representing the . + internal static FormattableString GetConstantFormattable(this Constant constant, bool writeAsString = false) + { + if (constant.Value == null) + { + // Cast helps the overload resolution + return $"({constant.Type}){null:L}"; + } + + if (constant.IsNewInstanceSentinel) + { + return $"new {constant.Type}()"; + } + + if (constant.Value is Constant.Expression expression) + { + return expression.ExpressionValue; + } + // TO-DO: Implement once enum types are implemented : https://github.com/Azure/autorest.csharp/issues/4198 + //if (constant is { Type: { IsFrameworkType: false }, Value: EnumTypeValue enumTypeValue }) + //{ + // return $"{constant.Type}.{enumTypeValue.Declaration.Name}"; + //} + + + if (constant.Type is { IsFrameworkType: false, Implementation: EnumType enumType }) + { + if (enumType.IsStringValueType) + return $"new {constant.Type}({constant.Value:L})"; + else + return $"new {constant.Type}(({enumType.ValueType}){constant.Value})"; + } + + Type frameworkType = constant.Type.FrameworkType; + if (frameworkType == typeof(DateTimeOffset)) + { + var d = (DateTimeOffset)constant.Value; + d = d.ToUniversalTime(); + return $"new {typeof(DateTimeOffset)}({d.Year:L}, {d.Month:L}, {d.Day:L} ,{d.Hour:L}, {d.Minute:L}, {d.Second:L}, {d.Millisecond:L}, {typeof(TimeSpan)}.{nameof(TimeSpan.Zero)})"; + } + + if (frameworkType == typeof(byte[])) + { + var bytes = (byte[])constant.Value; + var joinedBytes = string.Join(", ", bytes); + return $"new byte[] {{{joinedBytes}}}"; + } + + if (frameworkType == typeof(ResourceType)) + { + return $"{((ResourceType)constant.Value).ToString():L}"; + } + + if (frameworkType == typeof(bool) && writeAsString) + { + return $"\"{constant.Value!.ToString()!.ToLower()}\""; + } + + return $"{constant.Value:L}"; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/NamedTypeSymbolExtensions.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/NamedTypeSymbolExtensions.cs new file mode 100644 index 0000000000..5e9f52b7b2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/NamedTypeSymbolExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Generator.CSharp +{ + internal static class NamedTypeSymbolExtensions + { + public static bool IsSameType(this INamedTypeSymbol symbol, CSharpType type) + { + if (type.IsValueType && type.IsNullable) + { + if (symbol.ConstructedFrom.SpecialType != SpecialType.System_Nullable_T) + return false; + return IsSameType((INamedTypeSymbol)symbol.TypeArguments.Single(), type.WithNullable(false)); + } + + if (symbol.ContainingNamespace.ToString() != type.Namespace || symbol.Name != type.Name || symbol.TypeArguments.Length != type.Arguments.Count) + { + return false; + } + + for (int i = 0; i < type.Arguments.Count; ++i) + { + if (!IsSameType((INamedTypeSymbol)symbol.TypeArguments[i], type.Arguments[i])) + { + return false; + } + } + return true; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/StringExtensions.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/StringExtensions.cs new file mode 100644 index 0000000000..227d0e8977 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/StringExtensions.cs @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.Generator.CSharp +{ + internal static class StringExtensions + { + private static bool IsWordSeparator(char c) => !SyntaxFacts.IsIdentifierPartCharacter(c) || c == '_'; + + [return: NotNullIfNotNull("name")] + public static string ToCleanName(this string name, bool isCamelCase = true) + { + if (string.IsNullOrEmpty(name)) + { + return name; + } + StringBuilder nameBuilder = new StringBuilder(); + + int i = 0; + + if (char.IsDigit(name[0])) + { + nameBuilder.Append("_"); + } + else + { + while (!SyntaxFacts.IsIdentifierStartCharacter(name[i])) + { + i++; + } + } + + bool upperCase = false; + int firstWordLength = 1; + for (; i < name.Length; i++) + { + var c = name[i]; + if (IsWordSeparator(c)) + { + upperCase = true; + continue; + } + + if (nameBuilder.Length == 0 && isCamelCase) + { + c = char.ToUpper(c); + upperCase = false; + } + else if (nameBuilder.Length < firstWordLength && !isCamelCase) + { + c = char.ToLower(c); + upperCase = false; + // grow the first word length when this letter follows by two other upper case letters + // this happens in OSProfile, where OS is the first word + if (i + 2 < name.Length && char.IsUpper(name[i + 1]) && (char.IsUpper(name[i + 2]) || IsWordSeparator(name[i + 2]))) + firstWordLength++; + // grow the first word length when this letter follows by another upper case letter and an end of the string + // this happens when the string only has one word, like OS, DNS + if (i + 2 == name.Length && char.IsUpper(name[i + 1])) + firstWordLength++; + } + + if (upperCase) + { + c = char.ToUpper(c); + upperCase = false; + } + + nameBuilder.Append(c); + } + + return nameBuilder.ToString(); + } + public static GetPathPartsEnumerator GetPathParts(string? path) => new GetPathPartsEnumerator(path); + + public ref struct GetPathPartsEnumerator + { + private ReadOnlySpan _path; + public Part Current { get; private set; } + + public GetPathPartsEnumerator(ReadOnlySpan path) + { + _path = path; + Current = default; + } + + public GetPathPartsEnumerator GetEnumerator() => this; + + public bool MoveNext() + { + var span = _path; + if (span.Length == 0) + { + return false; + } + + var separatorIndex = span.IndexOfAny('{', '}'); + + if (separatorIndex == -1) + { + Current = new Part(span, true); + _path = ReadOnlySpan.Empty; + return true; + } + + var separator = span[separatorIndex]; + // Handle {{ and }} escape sequences + if (separatorIndex + 1 < span.Length && span[separatorIndex + 1] == separator) + { + Current = new Part(span.Slice(0, separatorIndex + 1), true); + _path = span.Slice(separatorIndex + 2); + return true; + } + + var isLiteral = separator == '{'; + + // Skip empty literals + if (isLiteral && separatorIndex == 0 && span.Length > 1) + { + separatorIndex = span.IndexOf('}'); + if (separatorIndex == -1) + { + Current = new Part(span.Slice(1), true); + _path = ReadOnlySpan.Empty; + return true; + } + + Current = new Part(span.Slice(1, separatorIndex - 1), false); + } + else + { + Current = new Part(span.Slice(0, separatorIndex), isLiteral); + } + + _path = span.Slice(separatorIndex + 1); + return true; + } + + public readonly ref struct Part + { + public Part(ReadOnlySpan span, bool isLiteral) + { + Span = span; + IsLiteral = isLiteral; + } + + public ReadOnlySpan Span { get; } + public bool IsLiteral { get; } + + public void Deconstruct(out ReadOnlySpan span, out bool isLiteral) + { + span = Span; + isLiteral = IsLiteral; + } + + public void Deconstruct(out ReadOnlySpan span, out bool isLiteral, out int argumentIndex) + { + span = Span; + isLiteral = IsLiteral; + + if (IsLiteral) + { + argumentIndex = -1; + } + else + { + var formatSeparatorIndex = span.IndexOf(':'); + var indexSpan = formatSeparatorIndex == -1 ? span : span.Slice(0, formatSeparatorIndex); + argumentIndex = int.Parse(indexSpan); + } + } + } + } + + /// + /// Determines if the given name is a C# keyword. + /// + /// The string name of the keyword. + /// true if the string is a csharp keyword. + public static bool IsCSharpKeyword(string? name) + { + if (name == null) + { + return false; + } + + SyntaxKind kind = SyntaxFacts.GetKeywordKind(name); + if (kind == SyntaxKind.None) + { + kind = SyntaxFacts.GetContextualKeywordKind(name); + } + + return SyntaxFacts.IsKeywordKind(kind); + } + + public static string FirstCharToUpperCase(this string str) + { + if (string.IsNullOrEmpty(str)) + return str; + + var strSpan = str.AsSpan(); + + if (char.IsUpper(strSpan[0])) + return str; + + Span span = stackalloc char[strSpan.Length]; + strSpan.CopyTo(span); + span[0] = char.ToUpper(span[0]); + return new string(span); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/ValidationType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/ValidationType.cs new file mode 100644 index 0000000000..aa4ee34dbb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/ValidationType.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp +{ + /// + /// The set of validation types to perform on a parameter. + /// + public enum ValidationType + { + None, + AssertNotNull, + AssertNotNullOrEmpty + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/WorkspaceMetadataReferenceResolver.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/WorkspaceMetadataReferenceResolver.cs new file mode 100644 index 0000000000..2faea7b938 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/WorkspaceMetadataReferenceResolver.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Generator.CSharp +{ + /// + /// Resolves metadata references for a workspace. + /// + internal class WorkspaceMetadataReferenceResolver : MetadataReferenceResolver + { + // The set of unique directory paths to probe for assemblies. + private readonly HashSet _probingPaths; + + /// + /// Initializes a new instance of the class + /// and populates the probing paths with the directories of the trusted assemblies. + /// + internal WorkspaceMetadataReferenceResolver() + { + IReadOnlyList trustedAssemblies = ((string?)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") ?? "").Split(Path.PathSeparator); + HashSet probingPaths = new(); + + foreach (var assembly in trustedAssemblies) + { + var directory = Path.GetDirectoryName(assembly); + if (directory != null) + { + probingPaths.Add(directory); + } + } + + _probingPaths = probingPaths; + } + + internal bool Equals(WorkspaceMetadataReferenceResolver? other) + { + return ReferenceEquals(this, other); + } + + public override bool ResolveMissingAssemblies => true; + + public override bool Equals(object? other) => Equals(other as WorkspaceMetadataReferenceResolver); + + public override int GetHashCode() + { + return RuntimeHelpers.GetHashCode(_probingPaths); + } + + /// + /// Attempts to resolve a missing assembly reference for the specified definition and reference identity. + /// The resolver will attempt to locate the assembly in the probing paths . If the assembly + /// is found, a is created and returned; otherwise, is returned. + /// + public override PortableExecutableReference? ResolveMissingAssembly(MetadataReference definition, AssemblyIdentity referenceIdentity) + { + // check the probing paths + foreach (var path in _probingPaths) + { + var assemblyPath = Path.Combine(path, referenceIdentity.Name + ".dll"); + if (FileExists(assemblyPath)) + { + return MetadataReference.CreateFromFile(assemblyPath); + } + } + + return null; + } + + public override ImmutableArray ResolveReference(string reference, string? baseFilePath, MetadataReferenceProperties properties) + { + throw new NotImplementedException(); + } + + /// + /// A wrapper around . + /// + /// The path to validate for existence. + /// true if the specified file path exists. + internal virtual bool FileExists(string path) + { + return File.Exists(path); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriter.cs new file mode 100644 index 0000000000..0a99c677ac --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriter.cs @@ -0,0 +1,1387 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp; +using System.Linq; +using System.Text; +using System.Runtime.CompilerServices; +using static Microsoft.Generator.CSharp.ValidationType; +using Microsoft.Generator.CSharp.Expressions; +using Microsoft.CodeAnalysis; +using static Microsoft.Generator.CSharp.Expressions.Snippets; +using Microsoft.Generator.CSharp.Models; + +namespace Microsoft.Generator.CSharp +{ + public sealed class CodeWriter + { + private const int DefaultLength = 1024; + private static readonly string _newLine = "\n"; + private static readonly string _braceNewLine = "{\n"; + + private readonly HashSet _usingNamespaces = new HashSet(); + + private readonly Stack _scopes; + private string? _currentNamespace; + private char[] _builder; + private int _length; + private bool _writingXmlDocumentation; + + internal CodeWriter() + { + _builder = ArrayPool.Shared.Rent(DefaultLength); + + _scopes = new Stack(); + _scopes.Push(new CodeWriterScope(this, "", false)); + } + + public CodeWriterScope Scope(FormattableString line, string start = "{", string end = "}", bool newLine = true, CodeWriterScopeDeclarations? scopeDeclarations = null) + { + ValidateDeclarations(scopeDeclarations); + CodeWriterScope codeWriterScope = new CodeWriterScope(this, end, newLine); + _scopes.Push(codeWriterScope); + WriteLine(line); + WriteRawLine(start); + AddDeclarationsToScope(scopeDeclarations); + return codeWriterScope; + } + + public CodeWriterScope Scope() + { + return ScopeRaw(); + } + + private void ValidateDeclarations(CodeWriterScopeDeclarations? scopeDeclarations) + { + if (scopeDeclarations == null) + { + return; + } + + foreach (var declarationName in scopeDeclarations.Names) + { + if (!IsAvailable(declarationName)) + { + throw new InvalidOperationException($"Variable with name '{declarationName}' is declared already."); + } + } + } + + private void AddDeclarationsToScope(CodeWriterScopeDeclarations? scopeDeclarations) + { + if (scopeDeclarations == null) + { + return; + } + + var currentScope = _scopes.Peek(); + + foreach (var declarationName in scopeDeclarations.Names) + { + foreach (var scope in _scopes) + { + scope.AllDefinedIdentifiers.Add(declarationName); + } + + currentScope.Identifiers.Add(declarationName); + } + } + + private CodeWriterScope ScopeRaw(string start = "{", string end = "}", bool newLine = true) + { + WriteRawLine(start); + CodeWriterScope codeWriterScope = new CodeWriterScope(this, end, newLine); + _scopes.Push(codeWriterScope); + return codeWriterScope; + } + + private static void AppendTypeWithShortNames(CSharpType type, StringBuilder sb) + { + sb.Append(type.TryGetCSharpFriendlyName(out var keywordName) ? keywordName : type.Name); + + if (type.Arguments.Any()) + { + sb.Append("{"); + foreach (var typeArgument in type.Arguments) + { + AppendTypeWithShortNames(typeArgument, sb); + sb.Append(","); + } + sb.Remove(sb.Length - 1, 1); + sb.Append("}"); + } + + if (type is { IsNullable: true, IsValueType: true }) + { + sb.Append("?"); + } + } + + private CodeWriter WriteXmlDocumentationParametersExceptions(Type exceptionType, IReadOnlyCollection parameters, string reason) + { + if (parameters.Count == 0) + { + return this; + } + + var formatBuilder = new StringBuilder(); + for (var i = 0; i < parameters.Count - 2; ++i) + { + formatBuilder.Append(", "); + } + + if (parameters.Count > 1) + { + formatBuilder.Append(" or "); + } + + formatBuilder.Append(""); + formatBuilder.Append(reason); + + var description = FormattableStringFactory.Create(formatBuilder.ToString(), parameters.Select(p => (object)p.Name).ToArray()); + return WriteXmlDocumentationException(exceptionType, description); + } + + public CodeWriterScope SetNamespace(string @namespace) + { + _currentNamespace = @namespace; + WriteLine($"namespace {@namespace}"); + return Scope(); + } + + public CodeWriter Append(FormattableString formattableString) + { + if (formattableString.ArgumentCount == 0) + { + return AppendRaw(formattableString.ToString()); + } + + const string literalFormatString = ":L"; + const string declarationFormatString = ":D"; // :D :) + const string identifierFormatString = ":I"; + const string crefFormatString = ":C"; // wraps content into "see cref" tag, available only in xmlDoc + foreach ((var span, bool isLiteral, int index) in StringExtensions.GetPathParts(formattableString.Format)) + { + if (isLiteral) + { + AppendRaw(span); + continue; + } + + var argument = formattableString.GetArgument(index); + var isDeclaration = span.EndsWith(declarationFormatString); + var isIdentifier = span.EndsWith(identifierFormatString); + var isLiteralFormat = span.EndsWith(literalFormatString); + var isCref = span.EndsWith(crefFormatString); + + if (isCref) + { + if (!_writingXmlDocumentation) + { + throw new InvalidOperationException($"':C' formatter can be used only inside XmlDoc"); + } + + switch (argument) + { + case Type t: + AppendTypeForCRef(new CSharpType(t)); + break; + case CSharpType t: + AppendTypeForCRef(t); + break; + default: + Append($""); + break; + } + + continue; + } + + switch (argument) + { + case IEnumerable fss: + foreach (var fs in fss) + { + Append(fs); + } + break; + case FormattableString fs: + Append(fs); + break; + case Type t: + AppendType(new CSharpType(t), false, false); + break; + case CSharpType t: + AppendType(t, isDeclaration, false); + break; + case CodeWriterDeclaration declaration when isDeclaration: + WriteDeclaration(declaration); + break; + case CodeWriterDeclaration declaration: + Append(declaration); + break; + case ValueExpression expression: + expression.Write(this); + break; + case var _ when isLiteralFormat: + WriteLiteral(argument); + break; + default: + string? s = argument?.ToString(); + if (s == null) + { + throw new ArgumentNullException(index.ToString()); + } + + if (isDeclaration) + { + WriteDeclaration(s); + } + else if (isIdentifier) + { + WriteIdentifier(s); + } + else + { + AppendRaw(s); + } + break; + } + } + + return this; + } + + /// + /// A wrapper around to allow for writing method body statements. + /// This method will call the extension method of the plugin with the current instance of + /// and attempt to write . + /// + /// The to write. + public void WriteMethod(Method method) + { + CodeModelPlugin.Instance.CodeWriterExtensionMethods.WriteMethod(this, method); + } + + public void WriteProperty(PropertyDeclaration property) + { + if (property.Description is not null) + { + WriteLine().WriteXmlDocumentationSummary(property.Description); + } + + if (property.Exceptions is not null) + { + foreach (var (exceptionType, description) in property.Exceptions) + { + WriteXmlDocumentationException(exceptionType, description); + } + } + + var modifiers = property.Modifiers; + AppendRawIf("public ", modifiers.HasFlag(MethodSignatureModifiers.Public)) + .AppendRawIf("protected ", modifiers.HasFlag(MethodSignatureModifiers.Protected)) + .AppendRawIf("internal ", modifiers.HasFlag(MethodSignatureModifiers.Internal)) + .AppendRawIf("private ", modifiers.HasFlag(MethodSignatureModifiers.Private)) + .AppendRawIf("override ", modifiers.HasFlag(MethodSignatureModifiers.Override)) + .AppendRawIf("static ", modifiers.HasFlag(MethodSignatureModifiers.Static)) + .AppendRawIf("virtual ", modifiers.HasFlag(MethodSignatureModifiers.Virtual)); + + Append($"{property.PropertyType} "); + if (property is IndexerDeclaration indexer) + { + Append($"this[{indexer.IndexerParameter.Type} {indexer.IndexerParameter.Name}]"); + } + else + { + if (property.ExplicitInterface is not null) + { + Append($"{property.ExplicitInterface}."); + } + Append($"{property.Declaration:I}"); + } + + switch (property.PropertyBody) + { + case ExpressionPropertyBody(var expression): + expression.Write(AppendRaw(" => ")); + AppendRaw(";"); + break; + case AutoPropertyBody(var hasSetter, var setterModifiers, var initialization): + AppendRaw("{ get; "); + if (hasSetter) + { + WritePropertyAccessorModifiers(setterModifiers); + AppendRaw(" set; "); + } + AppendRaw("}"); + if (initialization is not null) + { + initialization.Write(AppendRaw(" = ")); + } + break; + case MethodPropertyBody(var getter, var setter, var setterModifiers): + WriteRawLine("{"); + // write getter + WriteMethodPropertyAccessor("get", getter); + // write setter + if (setter is not null) + { + WriteMethodPropertyAccessor("set", setter, setterModifiers); + } + AppendRaw("}"); + break; + default: + throw new InvalidOperationException($"Unhandled property body type {property.PropertyBody}"); + } + + WriteLine(); + + void WriteMethodPropertyAccessor(string name, MethodBodyStatement body, MethodSignatureModifiers modifiers = MethodSignatureModifiers.None) + { + WritePropertyAccessorModifiers(modifiers); + WriteRawLine(name); + WriteRawLine("{"); + using (AmbientScope()) + { + body.Write(this); + } + WriteRawLine("}"); + } + + void WritePropertyAccessorModifiers(MethodSignatureModifiers modifiers) + { + AppendRawIf("protected ", modifiers.HasFlag(MethodSignatureModifiers.Protected)) + .AppendRawIf("internal ", modifiers.HasFlag(MethodSignatureModifiers.Internal)) + .AppendRawIf("private ", modifiers.HasFlag(MethodSignatureModifiers.Private)); + } + } + + public void UseNamespace(string @namespace) + { + if (_currentNamespace == @namespace) + { + return; + } + + _usingNamespaces.Add(@namespace); + } + + public CodeWriter AppendIf(FormattableString formattableString, bool condition) + { + if (condition) + { + Append(formattableString); + } + + return this; + } + + public CodeWriter AppendRawIf(string str, bool condition) + { + if (condition) + { + AppendRaw(str); + } + + return this; + } + + public CodeWriter WriteReferenceOrConstant(ReferenceOrConstant value) + => Append(value.GetReferenceOrConstantFormattable()); + + public void WriteParameter(Parameter clientParameter) + { + if (clientParameter.Attributes.Any()) + { + AppendRaw("["); + foreach (var attribute in clientParameter.Attributes) + { + Append($"{attribute.Type}, "); + } + RemoveTrailingComma(); + AppendRaw("]"); + } + + AppendRawIf("out ", clientParameter.IsOut); + AppendRawIf("ref ", clientParameter.IsRef); + + Append($"{clientParameter.Type} {clientParameter.Name:D}"); + if (clientParameter.DefaultValue != null) + { + var defaultValue = clientParameter.DefaultValue.Value; + if (defaultValue.IsNewInstanceSentinel && defaultValue.Type.IsValueType || clientParameter.IsApiVersionParameter && clientParameter.Initializer != null) + { + Append($" = default"); + } + else + { + Append($" = {clientParameter.DefaultValue.Value.GetConstantFormattable()}"); + } + } + + AppendRaw(","); + } + + public CodeWriter WriteField(FieldDeclaration field, bool declareInCurrentScope = true) + { + if (field.Description != null) + { + WriteLine().WriteXmlDocumentationSummary(field.Description); + } + + var modifiers = field.Modifiers; + + if (field.WriteAsProperty) + { + AppendRaw(modifiers.HasFlag(FieldModifiers.Public) ? "public " : (modifiers.HasFlag(FieldModifiers.Internal) ? "internal " : "private ")); + } + else + { + AppendRaw(modifiers.HasFlag(FieldModifiers.Public) ? "public " : (modifiers.HasFlag(FieldModifiers.Internal) ? "internal " : "private ")) + .AppendRawIf("const ", modifiers.HasFlag(FieldModifiers.Const)) + .AppendRawIf("static ", modifiers.HasFlag(FieldModifiers.Static)) + .AppendRawIf("readonly ", modifiers.HasFlag(FieldModifiers.ReadOnly)); + } + + if (declareInCurrentScope) + { + Append($"{field.Type} {field.Declaration:D}"); + } + else + { + Append($"{field.Type} {field.Declaration:I}"); + } + + if (field.WriteAsProperty) + { + AppendRaw(modifiers.HasFlag(FieldModifiers.ReadOnly) ? "{ get; }" : "{ get; set; }"); + } + + if (field.InitializationValue != null && + (modifiers.HasFlag(FieldModifiers.Const) || modifiers.HasFlag(FieldModifiers.Static))) + { + AppendRaw(" = "); + field.InitializationValue.Write(AppendRaw(" = ")); + return WriteLine($";"); + } + + return field.WriteAsProperty ? WriteLine() : WriteLine($";"); + } + + public CodeWriter WriteParameterNullChecks(IReadOnlyCollection parameters) + { + foreach (Parameter parameter in parameters) + { + WriteVariableAssignmentWithNullCheck(parameter.Name, parameter); + } + + WriteLine(); + return this; + } + + public void WriteVariableAssignmentWithNullCheck(string variableName, Parameter parameter) + { + var assignToSelf = parameter.Name == variableName; + if (parameter.Initializer != null) + { + if (assignToSelf) + { + WriteLine($"{variableName:I} ??= {parameter.Initializer};"); + } + else + { + WriteLine($"{variableName:I} = {parameter.Name:I} ?? {parameter.Initializer};"); + } + } + else if (parameter.Validation != ValidationType.None) + { + if (assignToSelf) + { + using (Scope($"if ({parameter.Name:I} == null)")) + { + WriteLine($"throw new {typeof(ArgumentNullException)}(nameof({parameter.Name:I}));"); + } + } + else + { + WriteLine($"{variableName:I} = {parameter.Name:I} ?? throw new {typeof(ArgumentNullException)}(nameof({parameter.Name:I}));"); + } + } + else if (!assignToSelf) + { + WriteLine($"{variableName:I} = {parameter.Name:I};"); + } + } + + public CodeWriter WriteParametersValidation(IEnumerable parameters) + { + foreach (Parameter parameter in parameters) + { + WriteParameterValidation(parameter); + } + + WriteLine(); + return this; + } + + private CodeWriter WriteParameterValidation(Parameter parameter) + { + if (parameter.Validation == None && parameter.Initializer != null) + { + return WriteLine($"{parameter.Name:I} ??= {parameter.Initializer};"); + } + + var validationStatement = Argument.ValidateParameter(parameter); + + validationStatement.Write(this); + + return this; + } + + public CodeWriter WriteXmlDocumentationInheritDoc(CSharpType? crefType = null) + => crefType == null + ? WriteLine($"/// ") + : WriteLine($"/// "); + + public CodeWriter WriteXmlDocumentationSummary(FormattableString? text) + { + return WriteXmlDocumentation("summary", text); + } + + public CodeWriter WriteXmlDocumentation(string tag, FormattableString? text) + { + return WriteDocumentationLines($"<{tag}>", $"", text); + } + + public CodeWriter WriteXmlDocumentationParameters(IEnumerable parameters) + { + foreach (var parameter in parameters) + { + WriteXmlDocumentationParameter(parameter); + } + + return this; + } + + public CodeWriter WriteXmlDocumentationParameter(string name, FormattableString? text) + { + return WriteDocumentationLines($"", $"", text); + } + + /// + /// Writes XML documentation for a parameter of a method using a "param" tag. + /// + /// Writer to which code is written to. + /// The definition of the parameter, including name and description. + /// + public CodeWriter WriteXmlDocumentationParameter(Parameter parameter) + { + return WriteXmlDocumentationParameter(parameter.Name, parameter.Description); + } + + public CodeWriter WriteXmlDocumentationException(CSharpType exception, FormattableString? description) + { + return WriteDocumentationLines($"", $"", description); + } + + public CodeWriter WriteXmlDocumentationReturns(FormattableString text) + { + return WriteDocumentationLines($"", $"", text); + } + + internal CodeWriter WriteXmlDocumentationInclude(string filename, MethodSignature methodSignature, out string memberId) + { + var sb = new StringBuilder(); + sb.Append(methodSignature.Name).Append("("); + foreach (var parameter in methodSignature.Parameters) + { + AppendTypeWithShortNames(parameter.Type, sb); + sb.Append(","); + } + + sb.Remove(sb.Length - 1, 1); + sb.Append(")"); + + memberId = sb.ToString(); + return WriteRawLine($"/// "); + } + + public CodeWriter WriteXmlDocumentationRequiredParametersException(IEnumerable parameters) + { + return WriteXmlDocumentationParametersExceptions(typeof(ArgumentNullException), parameters.Where(p => p.Validation is AssertNotNull or AssertNotNullOrEmpty).ToArray(), " is null."); + } + + public CodeWriter WriteXmlDocumentationNonEmptyParametersException(IEnumerable parameters) + { + return WriteXmlDocumentationParametersExceptions(typeof(ArgumentException), parameters.Where(p => p.Validation == AssertNotNullOrEmpty).ToArray(), " is an empty string, and was expected to be non-empty."); + } + + public CodeWriter WriteDocumentationLines(FormattableString startTag, FormattableString endTag, FormattableString? text) + => AppendXmlDocumentation(startTag, endTag, text ?? $""); + + internal CodeWriter WriteRawXmlDocumentation(FormattableString? content) + { + if (content is null) + return this; + + var lines = content.ToString().Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + var xmlLines = string.Join('\n', lines.Select(l => "/// " + l)); + AppendRaw(xmlLines); + WriteLine(); + return this; + } + + internal CodeWriter AppendXmlDocumentation(FormattableString startTag, FormattableString endTag, FormattableString content) + { + const string xmlDoc = "/// "; + const string xmlDocNewLine = "\n/// "; + + var commentStart = _length; + AppendRaw(CurrentLine.IsEmpty ? xmlDoc : xmlDocNewLine); + + var startTagStart = _length; + Append(startTag); + _writingXmlDocumentation = true; + + var contentStart = _length; + if (content.Format.Length > 0) + { + Append(content); + } + var contentEnd = _length; + + _writingXmlDocumentation = false; + Append(endTag); + + if (contentStart == contentEnd) + { + var startTagSpan = WrittenText.Slice(startTagStart + 1, contentStart - startTagStart - 1); + var endTagSpan = WrittenText.Slice(contentEnd + 2); + + if (startTagSpan.SequenceEqual(endTagSpan)) + { + // Remove empty tags + _length = commentStart; + } + else + { + WriteLine(); + } + + return this; + } + + WriteLine(); + var contentSpan = _builder.AsSpan(contentStart, contentEnd - contentStart); + + var lastLineBreak = contentSpan.LastIndexOf(_newLine); + if (lastLineBreak == -1) + { + // Add spaces and dot to match existing formatting + if (contentEnd > contentStart) + { + if (contentSpan[^1] != ' ') + { + InsertRaw(contentSpan[^1] == '.' ? " " : ". ", contentEnd); + } + else + { + var trimmedContentSpan = contentSpan.TrimEnd(); + if (trimmedContentSpan[^1] != '.') + { + InsertRaw(".", contentStart + trimmedContentSpan.Length); + } + } + + if (contentSpan[0] != ' ') + { + InsertRaw(" ", contentStart); + } + } + return this; + } + + if (lastLineBreak != contentSpan.Length) + { + InsertRaw(xmlDocNewLine, contentEnd); + } + + while (lastLineBreak != -1) + { + InsertRaw(xmlDoc, lastLineBreak + contentStart + 1); + contentSpan = contentSpan.Slice(0, lastLineBreak); + lastLineBreak = contentSpan.LastIndexOf(_newLine); + } + + if (contentSpan.Length > 0) + { + InsertRaw(xmlDocNewLine, contentStart); + } + + return this; + } + + internal string GetTemporaryVariable(string s) + { + if (IsAvailable(s)) + { + return s; + } + + for (int i = 0; i < 100; i++) + { + var name = s + i; + if (IsAvailable(name)) + { + return name; + } + } + throw new InvalidOperationException("Can't find suitable variable name."); + } + + private bool IsAvailable(string s) + { + if (_scopes.TryPeek(out var currentScope)) + { + if (currentScope.AllDefinedIdentifiers.Contains(s)) + { + return false; + } + } + + foreach (CodeWriterScope codeWriterScope in _scopes) + { + if (codeWriterScope.Identifiers.Contains(s)) + { + return false; + } + } + + return true; + } + + private void AppendTypeForCRef(CSharpType type) + { + // Because of the limitations of type cref in XmlDoc + // we add "?" nullability operator after `cref` block + var isNullable = type is { IsNullable: true, IsValueType: true }; + var arguments = type.IsGenericType ? type.Arguments : null; + + type = type.WithNullable(false); + if (type.IsGenericType) + { + type = type.GetGenericTypeDefinition(); + } + + AppendRaw($""); + + if (isNullable) + { + AppendRaw("?"); + } + + if (arguments is not null) + { + for (int i = 0; i < arguments.Count; i++) + { + var argument = arguments[i]; + if (argument is { IsFrameworkType: true, FrameworkType.IsGenericParameter: true }) + { + continue; + } + + AppendRaw(" where "); + AppendType(type.Arguments[i], false, false); + AppendRaw(" is"); + if (argument.IsArray) + { + AppendRaw(" an array of type "); + argument = argument.ElementType; + } + else + { + AppendRaw(" of type "); + } + + // If argument type is non-generic, we can provide "see cref" for it + // Otherwise, just write its name + if (argument.IsGenericType) + { + AppendRaw(""); + AppendType(argument, false, true); + AppendRaw(""); + } + else + { + AppendTypeForCRef(argument); + } + + AppendRaw(","); + } + RemoveTrailingComma(); + } + } + + private void AppendType(CSharpType type, bool isDeclaration, bool writeTypeNameOnly) + { + if (type.TryGetCSharpFriendlyName(out var keywordName)) + { + AppendRaw(keywordName); + } + else if (isDeclaration && !type.IsFrameworkType) + { + AppendRaw(type.Implementation.Name); + } + else if (writeTypeNameOnly) + { + AppendRaw(type.Name); + } + else + { + UseNamespace(type.Namespace); + + AppendRaw("global::"); + AppendRaw(type.Namespace); + AppendRaw("."); + AppendRaw(type.Name); + } + + if (type.Arguments.Any()) + { + AppendRaw(_writingXmlDocumentation ? "{" : "<"); + foreach (var typeArgument in type.Arguments) + { + AppendType(typeArgument, false, writeTypeNameOnly); + AppendRaw(_writingXmlDocumentation ? "," : ", "); + } + RemoveTrailingComma(); + AppendRaw(_writingXmlDocumentation ? "}" : ">"); + } + + if (!isDeclaration && type is { IsNullable: true, IsValueType: true }) + { + AppendRaw("?"); + } + } + + public CodeWriter WriteLiteral(object? o) + { + return AppendRaw(o switch + { + null => "null", + string s => SyntaxFactory.Literal(s).ToString(), + int i => SyntaxFactory.Literal(i).ToString(), + long l => SyntaxFactory.Literal(l).ToString(), + decimal d => SyntaxFactory.Literal(d).ToString(), + double d => SyntaxFactory.Literal(d).ToString(), + float f => SyntaxFactory.Literal(f).ToString(), + bool b => b ? "true" : "false", + BinaryData bd => bd.ToArray().Length == 0 ? "new byte[] { }" : SyntaxFactory.Literal(bd.ToString()).ToString(), + _ => throw new NotImplementedException() + }); + } + + public CodeWriter WriteLine(FormattableString formattableString) + { + Append(formattableString); + WriteLine(); + + return this; + } + + public CodeWriter WriteLine() + { + WriteRawLine(string.Empty); + + return this; + } + + private ReadOnlySpan WrittenText => _builder.AsSpan(0, _length); + + private ReadOnlySpan PreviousLine + { + get + { + var writtenText = WrittenText; + + var indexOfNewLine = writtenText.LastIndexOf(_newLine); + if (indexOfNewLine == -1) + { + return Span.Empty; + } + + var writtenTextBeforeLastLine = writtenText.Slice(0, indexOfNewLine); + var indexOfPreviousNewLine = writtenTextBeforeLastLine.LastIndexOf(_newLine); + if (indexOfPreviousNewLine == -1) + { + return writtenText.Slice(0, indexOfNewLine + 1); + } + + return writtenText.Slice(indexOfPreviousNewLine + 1, indexOfNewLine - indexOfPreviousNewLine); + } + } + + private ReadOnlySpan CurrentLine + { + get + { + var writtenText = WrittenText; + + var indexOfNewLine = writtenText.LastIndexOf(_newLine); + if (indexOfNewLine == -1) + { + return writtenText; + } + + return writtenText.Slice(indexOfNewLine + 1); + } + } + + private void EnsureSpace(int space) + { + if (_builder.Length - _length < space) + { + var newBuilder = ArrayPool.Shared.Rent(Math.Max(_builder.Length + space, _builder.Length * 2)); + _builder.AsSpan().CopyTo(newBuilder); + + ArrayPool.Shared.Return(_builder); + _builder = newBuilder; + } + } + + public CodeWriter WriteRawLine(string str) + { + AppendRaw(str); + + var previousLine = PreviousLine; + + if (CurrentLine.IsEmpty && + (previousLine.SequenceEqual(_newLine) || previousLine.EndsWith(_braceNewLine))) + { + return this; + } + + AppendRaw(_newLine); + + return this; + } + + public CodeWriter AppendRaw(string str) => AppendRaw(str.AsSpan()); + + private CodeWriter AppendRaw(ReadOnlySpan span) => InsertRaw(span, _length); + + private CodeWriter InsertRaw(ReadOnlySpan span, int position, bool skipNewLineCheck = false) + { + Debug.Assert(0 <= position); + Debug.Assert(position <= _length); + + if (!skipNewLineCheck) + { + var newLineSpan = "\r\n".AsSpan(); + var newLineIndex = span.IndexOf(newLineSpan); + while (newLineIndex != -1) + { + InsertRaw(span.Slice(0, newLineIndex), position, skipNewLineCheck: true); + position += newLineIndex; + span = span.Slice(newLineIndex + 1); + newLineIndex = span.IndexOf(newLineSpan); + } + } + + EnsureSpace(span.Length); + if (position < _length) + { + Array.Copy(_builder, position, _builder, span.Length + position, _length - position); + } + + span.CopyTo(_builder.AsSpan(position)); + _length += span.Length; + return this; + } + + internal CodeWriter WriteIdentifier(string identifier) + { + if (StringExtensions.IsCSharpKeyword(identifier)) + { + AppendRaw("@"); + } + return AppendRaw(identifier); + } + + internal CodeWriter WriteDeclaration(string declaration) + { + foreach (var scope in _scopes) + { + scope.AllDefinedIdentifiers.Add(declaration); + } + + _scopes.Peek().Identifiers.Add(declaration); + + return WriteIdentifier(declaration); + } + + public CodeWriter WriteDeclaration(CodeWriterDeclaration declaration) + { + if (_writingXmlDocumentation) + { + throw new InvalidOperationException("Can't declare variables inside documentation."); + } + + declaration.SetActualName(GetTemporaryVariable(declaration.RequestedName)); + _scopes.Peek().Declarations.Add(declaration); + return WriteDeclaration(declaration.ActualName); + } + + public IDisposable WriteMethodDeclaration(MethodSignatureBase methodBase, params string[] disabledWarnings) + { + var outerScope = WriteMethodDeclarationNoScope(methodBase, disabledWarnings); + WriteLine(); + var innerScope = Scope(); + return Disposable.Create(() => + { + innerScope.Dispose(); + outerScope.Dispose(); + }); + } + + public IDisposable WriteMethodDeclarationNoScope(MethodSignatureBase methodBase, params string[] disabledWarnings) + { + if (methodBase.Attributes is { } attributes) + { + foreach (var attribute in attributes) + { + if (attribute.Arguments.Any()) + { + Append($"[{attribute.Type}("); + foreach (var argument in attribute.Arguments) + { + argument.Write(this); + } + RemoveTrailingComma(); + WriteRawLine(")]"); + } + else + { + WriteLine($"[{attribute.Type}]"); + } + } + } + + foreach (var disabledWarning in disabledWarnings) + { + WriteLine($"#pragma warning disable {disabledWarning}"); + } + + AppendRawIf("public ", methodBase.Modifiers.HasFlag(MethodSignatureModifiers.Public)) + .AppendRawIf("internal ", methodBase.Modifiers.HasFlag(MethodSignatureModifiers.Internal)) + .AppendRawIf("protected ", methodBase.Modifiers.HasFlag(MethodSignatureModifiers.Protected)) + .AppendRawIf("private ", methodBase.Modifiers.HasFlag(MethodSignatureModifiers.Private)) + .AppendRawIf("static ", methodBase.Modifiers.HasFlag(MethodSignatureModifiers.Static)); + + if (methodBase is MethodSignature method) + { + AppendRawIf("virtual ", methodBase.Modifiers.HasFlag(MethodSignatureModifiers.Virtual)) + .AppendRawIf("override ", methodBase.Modifiers.HasFlag(MethodSignatureModifiers.Override)) + .AppendRawIf("new ", methodBase.Modifiers.HasFlag(MethodSignatureModifiers.New)) + .AppendRawIf("async ", methodBase.Modifiers.HasFlag(MethodSignatureModifiers.Async)); + + if (method.ReturnType != null) + { + Append($"{method.ReturnType} "); + } + else + { + AppendRaw("void "); + } + + if (method.ExplicitInterface is not null) + { + Append($"{method.ExplicitInterface}."); + } + + Append($"{methodBase.Name}"); + + if (method?.GenericArguments != null) + { + AppendRaw("<"); + foreach (var argument in method.GenericArguments) + { + Append($"{argument:D},"); + } + RemoveTrailingComma(); + AppendRaw(">"); + } + } + else + { + Append($"{methodBase.Name}"); + } + + AppendRaw("(") + .AppendRawIf("this ", methodBase.Modifiers.HasFlag(MethodSignatureModifiers.Extension)); + + var outerScope = AmbientScope(); + + foreach (var parameter in methodBase.Parameters) + { + WriteParameter(parameter); + } + + RemoveTrailingComma(); + Append($")"); + + if (methodBase is MethodSignature { GenericParameterConstraints: { } constraints }) + { + WriteLine(); + foreach (var constraint in constraints) + { + constraint.Write(this); + AppendRaw(" "); + } + } + + if (methodBase is ConstructorSignature { Initializer: { } } constructor) + { + var (isBase, arguments) = constructor.Initializer; + + if (!isBase || arguments.Any()) + { + AppendRaw(isBase ? ": base(" : ": this("); + foreach (var argument in arguments) + { + argument.Write(this); + AppendRaw(", "); + } + RemoveTrailingComma(); + AppendRaw(")"); + } + } + + foreach (var disabledWarning in disabledWarnings) + { + WriteLine(); + Append($"#pragma warning restore {disabledWarning}"); + } + + return outerScope; + } + + public sealed override string ToString() + { + if (_length == 0) + { + return string.Empty; + } + + var builder = new StringBuilder(_length); + IEnumerable namespaces = _usingNamespaces + .OrderByDescending(ns => ns.StartsWith("System")) + .ThenBy(ns => ns, StringComparer.Ordinal); + + string licenseString = CodeModelPlugin.Instance.CodeWriterExtensionMethods.LicenseString; + builder.Append(licenseString); + builder.Append("// "); + builder.Append(_newLine); + builder.Append(_newLine); + builder.Append("#nullable disable"); + builder.Append(_newLine); + builder.Append(_newLine); + + foreach (string ns in namespaces) + { + builder.Append("using ").Append(ns).Append(";").Append(_newLine); + } + + if (namespaces.Any()) + { + builder.Append(_newLine); + } + + // Normalize newlines + var spanLines = _builder.AsSpan(0, _length).EnumerateLines(); + int lineCount = 0; + foreach (var line in spanLines) + { + builder.Append(line.TrimEnd()); + builder.Append(_newLine); + lineCount++; + } + // Remove last new line if there are more than 1 + if (lineCount > 1) + { + builder.Remove(builder.Length - _newLine.Length, _newLine.Length); + } + return builder.ToString(); + } + + public sealed class CodeWriterScope : IDisposable + { + private readonly CodeWriter _writer; + private readonly string? _end; + private readonly bool _newLine; + + internal HashSet Identifiers { get; } = new(); + + internal HashSet AllDefinedIdentifiers { get; } = new(); + + internal List Declarations { get; } = new(); + + internal CodeWriterScope(CodeWriter writer, string? end, bool newLine) + { + _writer = writer; + _end = end; + _newLine = newLine; + } + + public void Dispose() + { + if (_writer != null) + { + _writer.PopScope(this); + foreach (var declaration in Declarations) + { + declaration.SetActualName(null); + } + + Declarations.Clear(); + + if (_end != null) + { + _writer.TrimNewLines(); + _writer.AppendRaw(_end); + } + + if (_newLine) + { + _writer.WriteLine(); + } + } + } + } + + private void TrimNewLines() + { + while (PreviousLine.SequenceEqual(_newLine) && + CurrentLine.IsEmpty) + { + _length--; + } + } + + private void PopScope(CodeWriterScope expected) + { + var actual = _scopes.Pop(); + Debug.Assert(actual == expected); + } + + private int? FindLastNonWhitespaceCharacterIndex() + { + var text = WrittenText; + for (int i = text.Length - 1; i >= 0; i--) + { + if (char.IsWhiteSpace(text[i])) + { + continue; + } + + return i; + } + + return null; + } + + public void RemoveTrailingCharacter() + { + int? lastCharIndex = FindLastNonWhitespaceCharacterIndex(); + if (lastCharIndex.HasValue) + { + _length = lastCharIndex.Value; + } + } + + public void RemoveTrailingComma() + { + int? lastCharIndex = FindLastNonWhitespaceCharacterIndex(); + if (lastCharIndex.HasValue && WrittenText[lastCharIndex.Value] == ',') + { + _length = lastCharIndex.Value; + } + } + + public CodeWriterScope AmbientScope() + { + var codeWriterScope = new CodeWriterScope(this, null, false); + _scopes.Push(codeWriterScope); + return codeWriterScope; + } + + internal void Append(CodeWriterDeclaration declaration) + { + WriteIdentifier(declaration.ActualName); + } + + internal void WriteTypeModifiers(TypeSignatureModifiers modifiers) + { + AppendRawIf("public ", modifiers.HasFlag(TypeSignatureModifiers.Public)) + .AppendRawIf("internal ", modifiers.HasFlag(TypeSignatureModifiers.Internal)) + .AppendRawIf("private ", modifiers.HasFlag(TypeSignatureModifiers.Private)) + .AppendRawIf("static ", modifiers.HasFlag(TypeSignatureModifiers.Static)) + .AppendRawIf("sealed ", modifiers.HasFlag(TypeSignatureModifiers.Sealed)) + .AppendRawIf("partial ", modifiers.HasFlag(TypeSignatureModifiers.Partial)); // partial must be the last to write otherwise compiler will complain + } + + public void WriteTypeArguments(IEnumerable? typeArguments) + { + if (typeArguments is null) + { + return; + } + + AppendRaw("<"); + foreach (var argument in typeArguments) + { + Append($"{argument}, "); + } + + RemoveTrailingComma(); + AppendRaw(">"); + } + + public void WriteArguments(IEnumerable arguments, bool useSingleLine = true) + { + if (useSingleLine) + { + AppendRaw("("); + foreach (var argument in arguments) + { + argument.Write(this); + AppendRaw(", "); + } + + RemoveTrailingComma(); + AppendRaw(")"); + } + else + { + WriteRawLine("("); + foreach (var argument in arguments) + { + AppendRaw("\t"); + argument.Write(this); + WriteRawLine(","); + } + + RemoveTrailingCharacter(); + RemoveTrailingComma(); + AppendRaw(")"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriterDeclaration.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriterDeclaration.cs new file mode 100644 index 0000000000..7dfcb31d15 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriterDeclaration.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp +{ + public sealed class CodeWriterDeclaration + { + private string? _actualName; + private string? _debuggerName; + + public CodeWriterDeclaration(string name) + { + RequestedName = name; + } + + public string RequestedName { get; } + + public string ActualName => _actualName ?? _debuggerName ?? throw new InvalidOperationException($"Declaration {RequestedName} is not initialized"); + + internal void SetActualName(string? actualName) + { + if (_actualName != null && actualName != null) + { + throw new InvalidOperationException($"Declaration {_actualName} already initialized, can't initialize it with {actualName} name."); + } + + _actualName = actualName; + } + + internal void SetDebuggerName(string? debuggerName) + { + if (_actualName == null) + { + _debuggerName = debuggerName; + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriterExtensionMethods.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriterExtensionMethods.cs new file mode 100644 index 0000000000..c7dac7e692 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriterExtensionMethods.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Generator.CSharp +{ + /// + /// Contains extension methods for . + /// + public abstract class CodeWriterExtensionMethods + { + /// + /// The license string for the generated code to be written. + /// + public virtual string LicenseString => string.Empty; + + /// + /// Writes the given method to the writer. A valid instance of is required. + /// + /// The instance to write to. + /// The to write. + public virtual void WriteMethod(CodeWriter writer, Method method) + { + ArgumentNullException.ThrowIfNull(writer, nameof(writer)); + ArgumentNullException.ThrowIfNull(method, nameof(method)); + + if (method.Body is { } body) + { + using (writer.WriteMethodDeclaration(method.Signature)) + { + body.Write(writer); + } + } + else if (method.BodyExpression is { } expression) + { + using (writer.WriteMethodDeclarationNoScope(method.Signature)) + { + writer.AppendRaw(" => "); + expression.Write(writer); + writer.WriteRawLine(";"); + } + } + + writer.WriteLine(); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriterScopeDeclarations.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriterScopeDeclarations.cs new file mode 100644 index 0000000000..4c3280825c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/CodeWriterScopeDeclarations.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Generator.CSharp +{ + public sealed class CodeWriterScopeDeclarations + { + internal IReadOnlyList Names { get; } + + public CodeWriterScopeDeclarations(IEnumerable declarations) + { + var names = new List(); + foreach (var declaration in declarations) + { + declaration.SetActualName(declaration.RequestedName); + names.Add(declaration.ActualName); + } + + Names = names; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/ExpressionTypeProviderWriter.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/ExpressionTypeProviderWriter.cs new file mode 100644 index 0000000000..2b11bf06aa --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Writers/ExpressionTypeProviderWriter.cs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Linq; + +namespace Microsoft.Generator.CSharp.Writers +{ + public class ExpressionTypeProviderWriter + { + protected readonly TypeProvider _provider; + protected readonly CodeWriter _writer; + + public ExpressionTypeProviderWriter(CodeWriter writer, TypeProvider provider) + { + _provider = provider; + _writer = writer; + } + + public virtual void Write() + { + var ns = TypeProvider.GetDefaultModelNamespace(CodeModelPlugin.Instance.Configuration.Namespace); + using (_writer.SetNamespace(ns)) + { + WriteType(); + } + } + + private void WriteType() + { + if (_provider.IsEnum) + { + WriteEnum(); + } + else + { + WriteClassOrStruct(); + } + } + + private void WriteClassOrStruct() + { + _writer.WriteTypeModifiers(_provider.DeclarationModifiers); + if (_provider.IsStruct) + { + _writer.AppendRaw("struct "); + } + else + { + _writer.AppendRaw("class "); + } + _writer.Append($"{_provider.Type:D}") + .AppendRawIf(" : ", _provider.Inherits != null || _provider.Implements.Any()) + .AppendIf($"{_provider.Inherits},", _provider.Inherits != null); + + foreach (var implement in _provider.Implements) + { + _writer.Append($"{implement:D},"); + } + _writer.RemoveTrailingComma(); + + if (_provider.WhereClause is not null) + { + _provider.WhereClause.Write(_writer); + } + + _writer.WriteLine(); + using (_writer.Scope()) + { + _writer.WriteLine($"// Add Fields"); // https://github.com/Azure/autorest.csharp/issues/4475 + WriteFields(); + + _writer.WriteLine($"// Add Constructors"); // https://github.com/Azure/autorest.csharp/issues/4474 + WriteConstructors(); + + _writer.WriteLine($"// Add Properties"); // https://github.com/Azure/autorest.csharp/issues/4475 + WriteProperties(); + + _writer.WriteLine($"// Add Methods"); // https://github.com/Azure/autorest.csharp/issues/4476 + WriteMethods(); + + _writer.WriteLine($"// Add Nested Type"); + WriteNestedTypes(); + } + } + + private void WriteEnum() + { + _writer.WriteTypeModifiers(_provider.DeclarationModifiers); + _writer.Append($" enum {_provider.Type:D}") + .AppendRawIf(" : ", _provider.Inherits != null) + .AppendIf($"{_provider.Inherits}", _provider.Inherits != null); + + using (_writer.Scope()) + { + foreach (var field in _provider.Fields) + { + _writer.Append($"{field.Declaration:D}"); + if (field.InitializationValue != null) + { + _writer.AppendRaw(" = "); + field.InitializationValue.Write(_writer); + } + _writer.WriteRawLine(","); + } + _writer.RemoveTrailingComma(); + } + } + + protected virtual void WriteProperties() + { + foreach (var property in _provider.Properties) + { + _writer.WriteProperty(property); + _writer.WriteLine(); + } + _writer.WriteLine(); + } + + protected virtual void WriteFields() + { + foreach (var field in _provider.Fields) + { + _writer.WriteField(field, declareInCurrentScope: true); + } + _writer.WriteLine(); + } + + protected virtual void WriteConstructors() + { + foreach (var ctor in _provider.Constructors) + { + _writer.WriteMethod(ctor); + } + _writer.WriteLine(); + } + + protected virtual void WriteMethods() + { + foreach (var method in _provider.Methods) + { + _writer.WriteMethod(method); + } + _writer.WriteLine(); + } + + protected virtual void WriteNestedTypes() + { + foreach (var nested in _provider.NestedTypes) + { + var nestedWriter = new ExpressionTypeProviderWriter(_writer, nested); + nestedWriter.Write(); + _writer.WriteLine(); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/ApiTypesTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/ApiTypesTests.cs new file mode 100644 index 0000000000..d9d6185151 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/ApiTypesTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Moq; +using NUnit.Framework; + +namespace Microsoft.Generator.CSharp.Tests +{ + internal class ApiTypesTests + { +#pragma warning disable CS8618 +#pragma warning disable CS8602 + private Mock _mock; + + [SetUp] + public void Setup() + { + _mock = new Mock(); + } + [Test] + public void TestChangeTrackingListType() + { + Assert.IsNotNull(_mock?.Object); + + _mock.Setup(x => x.ChangeTrackingListType).Returns(typeof(int)); + Assert.AreEqual(typeof(int), _mock.Object.ChangeTrackingListType); + } + + [Test] + public void TestChangeTrackingDictionaryType() + { + _mock.Setup(x => x.ChangeTrackingDictionaryType).Returns(typeof(int)); + Assert.AreEqual(typeof(int), _mock.Object.ChangeTrackingDictionaryType); + } + + + [Test] + public void TestEndPointSampleValue() + { + Assert.IsNotNull(_mock?.Object); + _mock.Setup(x => x.EndPointSampleValue).Returns("Sample"); + Assert.AreEqual("Sample", _mock.Object.EndPointSampleValue); + } + + [Test] + public void TestResponseType() + { + Assert.IsNotNull(_mock?.Object); + _mock.Setup(x => x.ResponseType).Returns(typeof(int)); + Assert.AreEqual(typeof(int), _mock.Object.ResponseType); + } + + [Test] + public void TestResponseOfTType() + { + Assert.IsNotNull(_mock?.Object); + _mock.Setup(x => x.ResponseOfTType).Returns(typeof(int)); + Assert.AreEqual(typeof(int), _mock.Object.ResponseOfTType); + } + + [Test] + public void TestFromResponseName() + { + Assert.IsNotNull(_mock?.Object); + Assert.AreEqual("FromResponse", _mock.Object.FromResponseName); + } + + [Test] + public void TestResponseParameterName() + { + Assert.IsNotNull(_mock?.Object); + _mock.Setup(x => x.ResponseParameterName).Returns("Response"); + Assert.AreEqual("Response", _mock.Object.ResponseParameterName); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CSharpGenTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CSharpGenTests.cs new file mode 100644 index 0000000000..d5e20cc125 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CSharpGenTests.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Generator.CSharp.Expressions; +using Microsoft.Generator.CSharp.Input; +using Moq; +using NUnit.Framework; +using static Microsoft.Generator.CSharp.Expressions.ExtensibleSnippets; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class CSharpGenTests + { + private readonly string _mocksFolder = "./mocks"; + // Validates that the output path is parsed correctly when provided + [Test] + public void TestGetOutputPath_OutputPathProvided() + { + var outputPath = "./outputDir"; + var parsedOutputPath = CSharpGen.ParseOutputPath(outputPath).Replace("\\", "/"); + var expectedPath = $"{outputPath}/src"; + var areEqual = string.Equals(expectedPath, parsedOutputPath, StringComparison.OrdinalIgnoreCase); + + Assert.IsTrue(areEqual); + + // append 'src' to the output path and validate that it is not appended again + TestOutputPathAppended(outputPath, expectedPath); + } + + // Validates that the output path is parsed correctly when an empty string is provided + [Test] + public void TestGetConfigurationInputFilePath_DefaultPath() + { + var outputPath = ""; + var parsedOutputPath = CSharpGen.ParseOutputPath(outputPath).Replace("\\", "/"); + var expectedPath = $"src"; + var areEqual = string.Equals(expectedPath, parsedOutputPath, StringComparison.OrdinalIgnoreCase); + + Assert.IsTrue(areEqual); + } + + // Validates that a valid plugin implementation is accepted + [Test] + public void TestCSharpGen_ValidPlugin() + { + // mock plugin + var mockPlugin = new Mock(new GeneratorContext(Configuration.Load(_mocksFolder))) + { + CallBase = true + }; + + // mock type factory + var mockTypeFactory = new Mock() + { + CallBase = true + }; + + // mock api types + var mockApiTypes = new Mock() + { + CallBase = true + }; + + // mock extensible snippets + var mockExtensibleSnippets = new Mock() + { + CallBase = true + }; + + mockTypeFactory.Setup(p => p.CreateType(It.IsAny())).Returns(new CSharpType(typeof(IList<>))); + mockApiTypes.SetupGet(p => p.ChangeTrackingListType).Returns(typeof(IList<>)); + mockApiTypes.SetupGet(p => p.ChangeTrackingDictionaryType).Returns(typeof(IDictionary)); + mockExtensibleSnippets.SetupGet(p => p.Model).Returns(new Mock().Object); + mockPlugin.SetupGet(p => p.ApiTypes).Returns(mockApiTypes.Object); + mockPlugin.SetupGet(p => p.ExtensibleSnippets).Returns(mockExtensibleSnippets.Object); + mockPlugin.SetupGet(p => p.TypeFactory).Returns(mockTypeFactory.Object); + + var configFilePath = Path.Combine(_mocksFolder, "Configuration.json"); + var csharpGen = new CSharpGen().ExecuteAsync(); + + Assert.IsNotNull(csharpGen); + } + + private void TestOutputPathAppended(string outputPath, string expectedPath) + { + var srcPath = "/src"; + + outputPath += srcPath; + + + var parsedOutputPath = CSharpGen.ParseOutputPath(outputPath); + var cleanedOutputPath = parsedOutputPath.Replace("\\", "/"); + + var areEqual = string.Equals(expectedPath, cleanedOutputPath, StringComparison.OrdinalIgnoreCase); + + Assert.IsTrue(areEqual); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CSharpTypeTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CSharpTypeTests.cs new file mode 100644 index 0000000000..94ca94b760 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CSharpTypeTests.cs @@ -0,0 +1,451 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System; +using NUnit.Framework; +using System.Linq; +using System.Collections.Immutable; +using Moq; +using System.IO; +using System.Text; +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.Tests +{ + internal class CSharpTypeTests + { + private readonly string _mocksFolder = "mocks"; + private readonly string _autoGenerated = "// "; + private readonly string _licenseString = "// License string"; + private readonly string _nullableDisable = "#nullable disable"; + + [OneTimeSetUp] + public void Setup() + { + // mock api types + var mockApiTypes = new Mock() + { + CallBase = true + }; + + Mock extensibleSnippets = new Mock(); + + mockApiTypes.SetupGet(p => p.ChangeTrackingListType).Returns(typeof(IList<>)); + mockApiTypes.SetupGet(p => p.ChangeTrackingDictionaryType).Returns(typeof(IDictionary<,>)); + + var configFilePath = Path.Combine(AppContext.BaseDirectory, _mocksFolder); + // initialize the singleton instance of the plugin + _ = new MockCodeModelPlugin(new GeneratorContext(Configuration.Load(configFilePath))); + } + + [TestCase(typeof(int))] + [TestCase(typeof(IList<>))] + [TestCase(typeof(IList))] + [TestCase(typeof(IDictionary<,>))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>))] + public void TypesAreEqual(Type type) + { + var cst1 = new CSharpType(type); + var cst2 = new CSharpType(type); + Assert.IsTrue(cst1.Equals(cst2)); + } + + [TestCase(typeof(int))] + [TestCase(typeof(IList<>))] + [TestCase(typeof(IList))] + [TestCase(typeof(IDictionary<,>))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>))] + public void EqualToFrameworkType(Type type) + { + var cst = new CSharpType(type); + Assert.IsTrue(cst.Equals(type)); + } + + [TestCase(typeof(int))] + [TestCase(typeof(IList<>))] + [TestCase(typeof(IList))] + [TestCase(typeof(IDictionary<,>))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>))] + public void HashCodesAreEqual(Type type) + { + var cst1 = new CSharpType(type); + var cst2 = new CSharpType(type); + Assert.AreEqual(cst1.GetHashCode(), cst2.GetHashCode()); + } + + [TestCase(typeof(IList<>), new[] { typeof(int) })] + [TestCase(typeof(IReadOnlyList<>), new[] { typeof(string) })] + [TestCase(typeof(IDictionary<,>), new[] { typeof(string), typeof(string) })] + [TestCase(typeof(IReadOnlyDictionary<,>), new[] { typeof(string), typeof(int) })] + [TestCase(typeof(IDictionary<,>), new[] { typeof(string), typeof(IDictionary) })] + public void TypesAreEqualForGenericTypes(Type type, Type[] arguments) + { + var cstArguments = arguments.Select(t => (CSharpType)t); + // pass the arguments in as an array + var cst1 = new CSharpType(type, cstArguments.ToArray()); + // pass the arguments in as an `List` + var cst2 = new CSharpType(type, cstArguments.ToList()); + // pass the arguments in as an `ImmutableArray` + var cst3 = new CSharpType(type, cstArguments.ToImmutableArray()); + // pass the arguments in as an `ImmutableList` + var cst4 = new CSharpType(type, cstArguments.ToImmutableList()); + + Assert.IsTrue(cst1.Equals(cst2)); + Assert.IsTrue(cst2.Equals(cst3)); + Assert.IsTrue(cst3.Equals(cst4)); + + Assert.AreEqual(cst1.GetHashCode(), cst2.GetHashCode()); + Assert.AreEqual(cst2.GetHashCode(), cst3.GetHashCode()); + Assert.AreEqual(cst3.GetHashCode(), cst4.GetHashCode()); + } + + [TestCase(typeof(int), typeof(string))] + [TestCase(typeof(int), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(int))] + [TestCase(typeof(int), typeof(IList))] + [TestCase(typeof(IList), typeof(int))] + [TestCase(typeof(IList), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(IList))] + [TestCase(typeof(IList), typeof(IList))] + [TestCase(typeof(IList), typeof(ICollection))] + [TestCase(typeof(Tuple<>), typeof(Tuple<,>))] + [TestCase(typeof(IDictionary), typeof(Dictionary<,>))] + [TestCase(typeof(IDictionary), typeof(Dictionary))] + [TestCase(typeof(IDictionary), typeof(Dictionary))] + [TestCase(typeof(IDictionary), typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>), typeof(IDictionary, IDictionary>))] + public void TypesAreNotEqual(Type type1, Type type2) + { + var cst1 = new CSharpType(type1); + var cst2 = new CSharpType(type2); + Assert.IsFalse(cst1.Equals(cst2)); + } + + [TestCase(typeof(int))] + [TestCase(typeof(int))] + [TestCase(typeof(IList<>))] + [TestCase(typeof(IList))] + [TestCase(typeof(IList))] + [TestCase(typeof(IList))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>))] + public void TypesAreNotEqualWhenNullabilityIsDifferent(Type type) + { + var cst = new CSharpType(type); + var nullableCst = new CSharpType(type, isNullable: true); + + Assert.IsFalse(cst.Equals(nullableCst)); + Assert.AreNotEqual(cst.GetHashCode(), nullableCst.GetHashCode()); + } + + [TestCase(typeof(int), typeof(string))] + [TestCase(typeof(int), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(int))] + [TestCase(typeof(int), typeof(IList))] + [TestCase(typeof(IList), typeof(int))] + [TestCase(typeof(IList), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(IList))] + [TestCase(typeof(IList), typeof(IList))] + [TestCase(typeof(IList), typeof(ICollection))] + [TestCase(typeof(IDictionary), typeof(Dictionary))] + [TestCase(typeof(IDictionary), typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>), typeof(IDictionary, IDictionary>))] + public void NotEqualToFrameworkType(Type type1, Type type2) + { + var cst = new CSharpType(type1); + Assert.IsFalse(cst.Equals(type2)); + } + + [TestCase(typeof(int), typeof(string))] + [TestCase(typeof(int), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(int))] + [TestCase(typeof(int), typeof(IList))] + [TestCase(typeof(IList), typeof(int))] + [TestCase(typeof(IList), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(IList))] + [TestCase(typeof(IList), typeof(IList))] + [TestCase(typeof(IList), typeof(ICollection))] + [TestCase(typeof(IDictionary), typeof(Dictionary))] + [TestCase(typeof(IDictionary), typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>), typeof(IDictionary, IDictionary>))] + public void HashCodesAreNotEqual(Type type1, Type type2) + { + var cst1 = new CSharpType(type1); + var cst2 = new CSharpType(type2); + Assert.AreNotEqual(cst1.GetHashCode(), cst2.GetHashCode()); + } + + [TestCase(typeof(IDictionary), typeof(IDictionary<,>), false)] + [TestCase(typeof(IDictionary>), typeof(IDictionary<,>), false)] + [TestCase(typeof(KeyValuePair), typeof(KeyValuePair<,>), false)] + [TestCase(typeof(KeyValuePair), typeof(KeyValuePair<,>), true)] + public void GetGenericTypeDefinition(Type input, Type expected, bool isNullable) + { + var actual = new CSharpType(input, isNullable).GetGenericTypeDefinition(); + Assert.AreEqual(new CSharpType(expected, isNullable), actual); + CollectionAssert.AreEqual(actual.Arguments, input.GetGenericTypeDefinition().GetGenericArguments().Select(p => new CSharpType(p))); + } + + [TestCase] + public void GetGenericTypeDefinitionForConstructedType() + { + var actual = new CSharpType(typeof(List<>), arguments: typeof(string)).GetGenericTypeDefinition(); + Assert.AreEqual(new CSharpType(typeof(List<>)), actual); + } + + [TestCase(typeof(int), false)] + [TestCase(typeof(ReadOnlyMemory<>), true)] + public void IsReadOnlyMemory(Type type, bool expected) + { + var actual = new CSharpType(type).IsReadOnlyMemory; + Assert.AreEqual(expected, actual); + } + + [TestCase(typeof(int), false)] + [TestCase(typeof(IEnumerable<>), true)] + [TestCase(typeof(IReadOnlyList<>), true)] + [TestCase(typeof(IReadOnlyCollection<>), false)] + [TestCase(typeof(IList<>), false)] + public void IsReadOnlyList(Type type, bool expected) + { + var actual = new CSharpType(type).IsReadOnlyList; + Assert.AreEqual(expected, actual); + } + + [TestCase(typeof(int), false)] + [TestCase(typeof(IEnumerable<>), false)] + [TestCase(typeof(IReadOnlyList<>), false)] + [TestCase(typeof(IReadOnlyCollection<>), false)] + [TestCase(typeof(IList<>), true)] + [TestCase(typeof(List<>), true)] + [TestCase(typeof(ICollection<>), true)] + public void IsReadWriteList(Type type, bool expected) + { + var actual = new CSharpType(type).IsReadWriteList; + Assert.AreEqual(expected, actual); + } + + [TestCase(typeof(int), false)] + [TestCase(typeof(IDictionary<,>), false)] + [TestCase(typeof(ReadOnlyMemory<>), true)] + [TestCase(typeof(IEnumerable<>), true)] + [TestCase(typeof(IReadOnlyList<>), true)] + [TestCase(typeof(IReadOnlyCollection<>), false)] + [TestCase(typeof(IList<>), true)] + [TestCase(typeof(List<>), true)] + [TestCase(typeof(ICollection<>), true)] + public void IsList(Type type, bool expected) + { + var actual = new CSharpType(type).IsList; + Assert.AreEqual(expected, actual); + } + + [TestCase(typeof(int), false)] + [TestCase(typeof(string[]), true)] + [TestCase(typeof(int[]), true)] + [TestCase(typeof(ReadOnlyMemory<>), false)] + [TestCase(typeof(List<>), false)] + public void IsArray(Type type, bool expected) + { + var actual = new CSharpType(type).IsArray; + Assert.AreEqual(expected, actual); + } + + [TestCase(typeof(int), false)] + [TestCase(typeof(IDictionary), false)] + [TestCase(typeof(ReadOnlyMemory<>), false)] + [TestCase(typeof(IReadOnlyDictionary<,>), true)] + public void IsReadOnlyDictionary(Type type, bool expected) + { + var actual = new CSharpType(type).IsReadOnlyDictionary; + Assert.AreEqual(expected, actual); + } + + [TestCase(typeof(int), false)] + [TestCase(typeof(IDictionary), true)] + [TestCase(typeof(ReadOnlyMemory<>), false)] + [TestCase(typeof(IReadOnlyDictionary<,>), false)] + public void IsReadWriteDictionary(Type type, bool expected) + { + var actual = new CSharpType(type).IsReadWriteDictionary; + Assert.AreEqual(expected, actual); + } + + [TestCase(typeof(int), false)] + [TestCase(typeof(IDictionary<,>), true)] + [TestCase(typeof(ReadOnlyMemory<>), false)] + [TestCase(typeof(IReadOnlyDictionary<,>), true)] + [TestCase(typeof(List<>), false)] + public void IsDictionary(Type type, bool expected) + { + var actual = new CSharpType(type).IsDictionary; + Assert.AreEqual(expected, actual); + } + + [TestCase(typeof(int), false)] + [TestCase(typeof(string[]), false)] + [TestCase(typeof(int[]), false)] + [TestCase(typeof(IReadOnlyCollection<>), false)] + [TestCase(typeof(IDictionary), true)] + [TestCase(typeof(IReadOnlyDictionary<,>), true)] + [TestCase(typeof(ReadOnlyMemory<>), true)] + [TestCase(typeof(IEnumerable<>), true)] + [TestCase(typeof(IReadOnlyList<>), true)] + [TestCase(typeof(IList<>), true)] + [TestCase(typeof(List<>), true)] + [TestCase(typeof(ICollection<>), true)] + public void IsCollectionType(Type type, bool expected) + { + var actual = new CSharpType(type).IsCollection; + Assert.AreEqual(expected, actual); + } + + [Test] + public void InitializationType_ReadOnlyMemory() + { + var arguments = typeof(int); + var cSharpType = new CSharpType(typeof(ReadOnlyMemory<>), arguments: arguments); + var actual = cSharpType.InitializationType; + var expected = new CSharpType(arguments.MakeArrayType()); + + var areEqual = actual.Equals(expected); + + Assert.IsTrue(areEqual); + } + + [Test] + public void InitializationType_List() + { + var arguments = typeof(int); + var listType = new CSharpType(typeof(IList<>), arguments: arguments); + var actual = listType.InitializationType; + var expected = new CSharpType(typeof(List<>), arguments: arguments); + + var areEqual = actual.Equals(expected); + + Assert.IsTrue(areEqual); + } + + [Test] + public void InitializationType_Dictionary() + { + var arguments = new CSharpType[] { typeof(string), typeof(int) }; + var cSharpType = new CSharpType(typeof(IDictionary<,>), arguments: arguments); + var actual = cSharpType.InitializationType; + var expected = new CSharpType(typeof(Dictionary<,>), arguments: arguments); + + var areEqual = actual.Equals(expected); + + Assert.IsTrue(areEqual); + } + + [Test] + public void PropertyInitializationType_ReadOnlyMemory() + { + var arguments = typeof(int); + var cSharpType = new CSharpType(typeof(ReadOnlyMemory<>), arguments: arguments); + var actual = cSharpType.PropertyInitializationType; + var expected = new CSharpType(typeof(ReadOnlyMemory<>), arguments: arguments); + + var areEqual = actual.Equals(expected); + + Assert.IsTrue(areEqual); + } + + [Test] + [Ignore("Disabled until API types are refactored")] + public void PropertyInitializationType_List() + { + var arguments = typeof(int); + var cSharpType = new CSharpType(typeof(IList<>), arguments: arguments); + var actual = cSharpType.PropertyInitializationType; + var expected = new CSharpType(typeof(IList<>), arguments: arguments); + + var areEqual = actual.Equals(expected); + + Assert.IsTrue(areEqual); + } + + [Test] + [Ignore("Disabled until API types are refactored")] + public void PropertyInitializationType_Dictionary() + { + var arguments = new CSharpType[] { typeof(string), typeof(int) }; + var cSharpType = new CSharpType(typeof(IDictionary<,>), arguments: arguments); + var actual = cSharpType.PropertyInitializationType; + var expected = new CSharpType(typeof(IDictionary<,>), arguments: arguments); + + var areEqual = actual.Equals(expected); + + Assert.IsTrue(areEqual); + } + + [Test] + public void GetElementType_ReadOnlyMemory() + { + var arguments = typeof(int); + var cSharpType = new CSharpType(typeof(ReadOnlyMemory<>), arguments: arguments); + var actual = cSharpType.ElementType; + var expected = cSharpType.Arguments[0]; + + var areEqual = actual.Equals(expected); + + Assert.IsTrue(areEqual); + } + + [Test] + public void GetElementType_Dictionary() + { + var key = typeof(int); + var value = typeof(string); + var cSharpType = new CSharpType(typeof(IDictionary<,>), arguments: [key, value]); + var actual = cSharpType.ElementType; + var expected = cSharpType.Arguments[1]; + + var areEqual = actual.Equals(expected); + + Assert.IsTrue(areEqual); + } + + [TestCase(typeof(int), typeof(string), typeof(float))] + public void ValidateUnion(params Type[] types) + { + var typesInUnion = types.Select(t => (CSharpType)t).ToArray(); + var type = CSharpType.FromUnion(typesInUnion, false); + Assert.AreEqual(types.Length, type.UnionItemTypes.Count); + for (int i = 0; i < typesInUnion.Length; i++) + { + Assert.AreEqual(typesInUnion[i], type.UnionItemTypes[i]); + } + } + + [TestCase(typeof(int), "int")] + [TestCase(typeof(string), "string")] + public void TestToString(Type type, string expectedString) + { + var cSharpType = new CSharpType(type); + var actual = cSharpType.ToString(); + var expected = new StringBuilder() + .Append(_licenseString) + .Append(_autoGenerated).Append(CodeWriterTests.NewLine) + .Append(CodeWriterTests.NewLine) + .Append(_nullableDisable).Append(CodeWriterTests.NewLine) + .Append(CodeWriterTests.NewLine) + .Append(expectedString).Append(CodeWriterTests.NewLine) + .ToString(); + + Assert.AreEqual(expected, actual); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CodeWriterExtensionTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CodeWriterExtensionTests.cs new file mode 100644 index 0000000000..4b8425aed3 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CodeWriterExtensionTests.cs @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Text; +using Moq; +using Microsoft.Generator.CSharp.Expressions; +using NUnit.Framework; +using System.Collections.Generic; +using System.IO; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class CodeWriterExtensionTests + { + private readonly string _mocksFolder = "mocks"; + private readonly string _licenseString = "// License string"; + private readonly string _autoGenerated = "// "; + private readonly string _nullableDisable = "#nullable disable"; + private string? _header; + + [OneTimeSetUp] + public void Setup() + { + Mock apiTypes = new Mock(); + Mock extensibleSnippets = new Mock(); + apiTypes.SetupGet(x => x.ResponseParameterName).Returns("result"); + + string outputFolder = "./outputFolder"; + string projectPath = outputFolder; + + _header = new StringBuilder() + .Append(_licenseString) + .Append(_autoGenerated).Append(CodeWriterTests.NewLine) + .Append(CodeWriterTests.NewLine) + .Append(_nullableDisable).Append(CodeWriterTests.NewLine) + .Append(CodeWriterTests.NewLine) + .ToString(); + + var configFilePath = Path.Combine(AppContext.BaseDirectory, _mocksFolder); + // initialize the singleton instance of the plugin + _ = new MockCodeModelPlugin(new GeneratorContext(Configuration.Load(configFilePath))); + } + + // Test that an exception is not thrown when the extension methods are null. + [Test] + public void NoExtensionMethods() + { + var writer = new CodeWriter(); + Assert.IsNotNull(writer); + } + + // Test the Write method for a cast expression using the default implementation. + [TestCase(typeof(int), 22.2, "((int)22.2)")] + [TestCase(typeof(double), 22, "((double)22)")] + [TestCase(typeof(string), 22, "((string)22)")] + public void TestWriteValueExpression_DefaultCastExpression(Type type, object inner, string expectedWritten) + { + var castExpression = new CastExpression(Snippets.Literal(inner), type); + var codeWriter = new CodeWriter(); + castExpression.Write(codeWriter); + + var sb = new StringBuilder(); + sb.Append(_header); + sb.Append(expectedWritten).Append(CodeWriterTests.NewLine); + + Assert.AreEqual(sb.ToString(), codeWriter.ToString()); + } + + // Test the Write method for a custom expression. + [Test] + public void TestWriteValueExpression_CustomExpression() + { + var mockCastExpression = new MockExpression(); + var codeWriter = new CodeWriter(); + mockCastExpression.Write(codeWriter); + + var sb = new StringBuilder(); + sb.Append(_header); + sb.Append("Custom implementation").Append(CodeWriterTests.NewLine); + + Assert.AreEqual(sb.ToString(), codeWriter.ToString()); + } + + // Test the Write method for a CollectionInitializerExpression using the default implementation. + [TestCase("foo", "{ \"foo\" }")] + [TestCase("bar", "{ \"bar\" }")] + public void TestWriteValueExpression_DefaultCollectionInitializerExpression(string literal, string expectedWritten) + { + var stringLiteralExpression = new StringLiteralExpression(literal, false); + CollectionInitializerExpression expression = new CollectionInitializerExpression(stringLiteralExpression); + var codeWriter = new CodeWriter(); + expression.Write(codeWriter); + + var sb = new StringBuilder(); + sb.Append(_header); + sb.Append(expectedWritten).Append(CodeWriterTests.NewLine); + + Assert.AreEqual(sb.ToString(), codeWriter.ToString()); + } + + // This test validates that the WriteMethod method works as expected using the extension method implementation in the mock code model plugin. + // A mock method with a body constructed from body statements is supplied. + [Test] + public void TestWriteMethodWithBodyStatements() + { + + bool useExpressionAsBody = false; + var method = ConstructMockMethod(useExpressionAsBody); + var codeWriter = new CodeWriter(); + + codeWriter.WriteMethod(method); + + var result = codeWriter.ToString(); + var expected = new StringBuilder() + .Append(_header) + .Append("Custom implementation").Append(CodeWriterTests.NewLine) + .ToString(); + + Assert.AreEqual(expected, result); + + } + + // This test validates that the WriteMethod method works as expected given a mock method with a body + // constructed from body expressions using the custom implementation of the extension methods in a mock code model plugin. + [Test] + public void TestWriteMethodWithBodyExpressions() + { + bool useExpressionAsBody = true; + var method = ConstructMockMethod(useExpressionAsBody); + var codeWriter = new CodeWriter(); + codeWriter.WriteMethod(method); + + var result = codeWriter.ToString(); + var expected = new StringBuilder() + .Append(_header) + .Append("Custom implementation").Append(CodeWriterTests.NewLine) + .ToString(); + + Assert.AreEqual(expected, result); + } + + // Construct a mock method with a body. The body can be either a list of statements or a single expression + // depending on the value of the useExpressionAsBody parameter. + private static Method ConstructMockMethod(bool useExpressionAsBody) + { + // create method signature + var methodName = "TestMethod"; + FormattableString summary = $"Sample summary for {methodName}"; + FormattableString description = $"Sample description for {methodName}"; + FormattableString returnDescription = $"Sample return description for {methodName}"; + var methodSignatureModifiers = MethodSignatureModifiers.Public; + var returnType = new CSharpType(typeof(BinaryData)); + var parameters = new List() + { + new Parameter("param1", $"Sample description for param1", new CSharpType(typeof(string)), null, Validation: ValidationType.AssertNotNullOrEmpty, Initializer: null) + }; + + Method method; + + if (!useExpressionAsBody) + { + var responseVar = new VariableReference(returnType, "responseParamName"); + var responseRef = Snippets.Var(responseVar, BinaryDataExpression.FromBytes(new StringLiteralExpression("sample response", false))); + var resultStatements = new List() + { + responseRef, + new KeywordStatement("return", responseVar) + }; + + method = new Method + ( + new MethodSignature(methodName, summary, description, methodSignatureModifiers, returnType, returnDescription, parameters), + resultStatements, + "GET" + ); + } + else + { + var response = BinaryDataExpression.FromBytes(new StringLiteralExpression("sample response", false)); + method = new Method + ( + new MethodSignature(methodName, summary, description, methodSignatureModifiers, returnType, returnDescription, parameters), + response, + "GET" + ); + } + + return method; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CodeWriterTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CodeWriterTests.cs new file mode 100644 index 0000000000..848b91bfa7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CodeWriterTests.cs @@ -0,0 +1,268 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework; +using Microsoft.Generator.CSharp.Expressions; +using System.IO; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class CodeWriterTests + { + internal const string NewLine = "\n"; + private CodeWriter _codeWriter = null!; + private readonly string _mocksFolder = "mocks"; + private readonly string _autoGenerated = "// "; + private readonly string _nullableDisable = "#nullable disable"; + private readonly string _licenseString = "License string"; + private string? _header; + + [SetUp] + public void Setup() + { + _codeWriter = new CodeWriter(); + + _header = new StringBuilder() + .Append("// ") + .Append(_licenseString) + .Append(_autoGenerated).Append(NewLine) + .Append(NewLine) + .Append(_nullableDisable).Append(NewLine) + .Append(NewLine) + .ToString(); + + var configFilePath = Path.Combine(AppContext.BaseDirectory, _mocksFolder); + // initialize the singleton instance of the plugin + _ = new MockCodeModelPlugin(new GeneratorContext(Configuration.Load(configFilePath))); + } + + [Test] + public void GeneratesNewNamesInChildScope() + { + var cwd1 = new CodeWriterDeclaration("a"); + var cwd2 = new CodeWriterDeclaration("a"); + _codeWriter.WriteLine($"{cwd1:D}"); + using (_codeWriter.Scope()) + { + _codeWriter.WriteLine($"{cwd2:D}"); + } + + var sb = new StringBuilder(); + sb.Append(_header); + sb.Append("a").Append(NewLine); + sb.Append("{").Append(NewLine); + sb.Append("a0").Append(NewLine); + sb.Append("}").Append(NewLine); + + Assert.AreEqual(sb.ToString(), _codeWriter.ToString()); + } + + [Test] + public void ScopeLineIsInsideScope() + { + var cwd1 = new CodeWriterDeclaration("a"); + var cwd2 = new CodeWriterDeclaration("a"); + using (_codeWriter.Scope($"{cwd1:D}")) + { + } + + using (_codeWriter.Scope($"{cwd2:D}")) + { + } + + var sb = new StringBuilder(); + sb.Append(_header); + sb.Append("a").Append(NewLine); + sb.Append("{").Append(NewLine); + sb.Append("}").Append(NewLine); + sb.Append("a").Append(NewLine); + sb.Append("{").Append(NewLine); + sb.Append("}").Append(NewLine); + + Assert.AreEqual(sb.ToString(), _codeWriter.ToString()); + } + + [Test] + public void VariableNameNotReusedWhenUsedInChildScope() + { + var cwd1 = new CodeWriterDeclaration("a"); + var cwd2 = new CodeWriterDeclaration("a"); + using (_codeWriter.Scope()) + { + _codeWriter.WriteLine($"{cwd1:D}"); + } + + _codeWriter.WriteLine($"{cwd2:D}"); + + var sb = new StringBuilder(); + sb.Append(_header); + sb.Append("{").Append(NewLine); + sb.Append("a").Append(NewLine); + sb.Append("}").Append(NewLine); + sb.Append("a0").Append(NewLine); + + Assert.AreEqual(sb.ToString(), _codeWriter.ToString()); + } + + [Test] + public void CorrectlyHandlesCurlyBraces() + { + _codeWriter.Append($"public {typeof(string)} Data {{ get; private set; }}"); + var expected = "public string Data { get; private set; }" + NewLine; + var sb = new StringBuilder(); + sb.Append(_header); + sb.Append(expected); + + Assert.AreEqual(sb.ToString(), + _codeWriter.ToString()); + } + + [Test] + public void FormatInFormat() + { + FormattableString fs1 = $"'1' is {typeof(int)}"; + FormattableString fs2 = $"'a' is {typeof(char)} and {fs1} and 'true' is {typeof(bool)}"; + + _codeWriter.Append(fs2); + var expected = "'a' is char and '1' is int and 'true' is bool" + NewLine; + var sb = new StringBuilder(); + sb.Append(_header); + sb.Append(expected); + + Assert.AreEqual(sb.ToString(), _codeWriter.ToString()); + } + + + [Test] + public void EnumerableFormatInFormat() + { + _codeWriter.Append($"Multiply:{Enumerable.Range(1, 4).Select(i => (FormattableString)$" {i} * 2 = {i * 2};")}"); + var expected = "Multiply: 1 * 2 = 2; 2 * 2 = 4; 3 * 2 = 6; 4 * 2 = 8;" + NewLine; + var sb = new StringBuilder(); + sb.Append(_header); + sb.Append(expected); + + Assert.AreEqual(sb.ToString(), _codeWriter.ToString()); + } + + [Test] + public void SingleLineSummary() + { + _codeWriter.WriteXmlDocumentationSummary($"Some {typeof(string)} summary."); + var expected = "/// Some string summary. " + NewLine; + var sb = new StringBuilder(); + sb.Append(_header); + sb.Append(expected); + + Assert.AreEqual(sb.ToString(), _codeWriter.ToString()); + } + + [Test] + public void NoEmptySummary() + { + _codeWriter.WriteXmlDocumentationSummary($"{string.Empty}"); + var expected = string.Empty; + var sb = new StringBuilder(); + sb.Append(expected); + + Assert.AreEqual(sb.ToString(), _codeWriter.ToString()); + } + + [TestCase(typeof(string), false, "", "")] + [TestCase(typeof(int), false, "", "")] + [TestCase(typeof(int), true, "?", "")] + [TestCase(typeof(List<>), false, "", "System.Collections.Generic")] + [TestCase(typeof(KeyValuePair<,>), false, "", "System.Collections.Generic")] + [TestCase(typeof(KeyValuePair), true, "? where TKey is of type , where TValue is of type ", "System.Collections.Generic")] + public void SeeCRefType(Type type, bool isNullable, string expectedWritten, string ns) + { + var csType = new CSharpType(type).WithNullable(isNullable); + _codeWriter.WriteXmlDocumentationSummary($"Some {csType:C} summary."); + var expected = $"/// Some {expectedWritten} summary. " + NewLine; + var sb = new StringBuilder(); + sb.Append(_header); + if (!string.IsNullOrEmpty(ns)) + { + sb.Append("using "); + sb.Append(ns); + sb.Append(";").Append(NewLine); + sb.Append(NewLine); + } + sb.Append(expected); + + Assert.AreEqual(sb.ToString(), _codeWriter.ToString()); + } + + [Test] + public void MultiLineSummary() + { + FormattableString fs1 = $@"L04 +L05 +L06 {typeof(int)} + + +L09"; + FormattableString fs2 = $@" + +L11 {typeof(bool)} +L12 + +"; + IEnumerable fss = new[] { fs1, fs2 }; + FormattableString fs = $@"L00 +L01 +L02 {typeof(string)} + +{fss} +L15 +L16"; + _codeWriter.WriteXmlDocumentationSummary(fs); + + var sb = new StringBuilder(); + sb.Append(_header); + sb.Append("/// ").Append(NewLine); + sb.Append("/// L00").Append(NewLine); + sb.Append("/// L01").Append(NewLine); + sb.Append("/// L02 string").Append(NewLine); + sb.Append("///").Append(NewLine); + sb.Append("/// L04").Append(NewLine); + sb.Append("/// L05").Append(NewLine); + sb.Append("/// L06 int").Append(NewLine); + sb.Append("///").Append(NewLine); + sb.Append("///").Append(NewLine); + sb.Append("/// L09").Append(NewLine); + sb.Append("///").Append(NewLine); + sb.Append("/// L11 bool").Append(NewLine); + sb.Append("/// L12").Append(NewLine); + sb.Append("///").Append(NewLine); + sb.Append("///").Append(NewLine); + sb.Append("/// L15").Append(NewLine); + sb.Append("/// L16").Append(NewLine); + sb.Append("/// ").Append(NewLine); + + Assert.AreEqual(sb.ToString(), _codeWriter.ToString()); + } + + // Validate that the WriteMethodDeclarationNoScope method correctly writes a custom constructor signature method + [Test] + public void TestWriteMethodDeclarationNoScope_ConstructorSignature() + { + var baseInitializerStatement = new ConstructorInitializer(true, new List { new StringLiteralExpression("test", false) }); + var constructorSignature = new ConstructorSignature(new CSharpType(typeof(string)), $"Test constructor summary", $"Test description", + MethodSignatureModifiers.Public, Array.Empty(), null, baseInitializerStatement); + var codeWriter = new CodeWriter(); + codeWriter.WriteMethodDeclarationNoScope(constructorSignature); + + var expected = new StringBuilder() + .Append(_header) + .Append("public String(): base(\"test\")").Append(NewLine) + .ToString(); + var result = codeWriter.ToString(); + Assert.AreEqual(expected, result); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CommandLineOptionsTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CommandLineOptionsTests.cs new file mode 100644 index 0000000000..e77a69854a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/CommandLineOptionsTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using CommandLine; +using NUnit.Framework; + +namespace Microsoft.Generator.CSharp.Tests +{ + internal class CommandLineOptionsTests + { + + /// + /// Test data for validating parsing different command line options. These test cases simply validate that the parser + /// does not throw an exception when parsing the command line options. + /// + private static IEnumerable TestParseCommandLineOptionsArgsTestData() + { + // happy path scenarios + yield return new TestCaseData(new string[] { "../inputDir", "--model-plugin", "my-plugin" }, false); + } + + // Validates parsing different command line options + [TestCaseSource(nameof(TestParseCommandLineOptionsArgsTestData))] + public void TestParseCommandLineOptionsArgs(string[] args, bool producesError) + { + var result = Parser.Default.ParseArguments(args); + Assert.IsNotNull(result); + Assert.IsNotNull(result.Value); + + Assert.IsTrue(result.Errors.Count() == 0); + } + + public static IEnumerable GetConfigurationInputFilePathTestCases + { + get + { + yield return new TestCaseData(new CommandLineOptions + { + OutputDirectory = "../myDir", + }, Path.Combine("../myDir", "Configuration.json")); + } + } + + public static IEnumerable ParseInputPathTestCases + { + get + { + yield return new TestCaseData("./src", $"{Path.Combine(Path.GetFullPath("./src"), Constants.DefaultGeneratedCodeFolderName)}"); + yield return new TestCaseData("../src", $"{Path.Combine(Path.GetFullPath("../src"), Constants.DefaultGeneratedCodeFolderName)}"); + yield return new TestCaseData("./Generated", Path.GetFullPath("./Generated")); + yield return new TestCaseData("c://Generated", Path.GetFullPath("c://Generated")); + yield return new TestCaseData("c://src///Generated", Path.GetFullPath("c://src///Generated")); + yield return new TestCaseData("./myDir", $"{Path.Combine(Path.GetFullPath("./myDir"), "src", Constants.DefaultGeneratedCodeFolderName)}"); + yield return new TestCaseData("./myDir/src", $"{Path.Combine(Path.GetFullPath("./myDir"), "src", Constants.DefaultGeneratedCodeFolderName)}"); + yield return new TestCaseData("./myDir/src/", $"{Path.Combine(Path.GetFullPath("./myDir"), "src", Constants.DefaultGeneratedCodeFolderName)}"); + yield return new TestCaseData("./myDir/src/Generated", $"{Path.Combine(Path.GetFullPath("./myDir"), "src", Constants.DefaultGeneratedCodeFolderName)}"); + yield return new TestCaseData("./myDir/src/Generated/", $"{Path.Combine(Path.GetFullPath("./myDir"), "src", Constants.DefaultGeneratedCodeFolderName)}\\"); + yield return new TestCaseData("c://someDir", $"{Path.Combine(Path.GetFullPath("c://someDir"), "src", Constants.DefaultGeneratedCodeFolderName)}"); + yield return new TestCaseData("c://someDir//src", $"{Path.Combine(Path.GetFullPath("c://someDir"), "src", Constants.DefaultGeneratedCodeFolderName)}"); + yield return new TestCaseData("c://someDir//src//", $"{Path.Combine(Path.GetFullPath("c://someDir"), "src", Constants.DefaultGeneratedCodeFolderName)}"); + yield return new TestCaseData("c://someDir//src//Generated", $"{Path.Combine(Path.GetFullPath("c://someDir"), "src", Constants.DefaultGeneratedCodeFolderName)}"); + yield return new TestCaseData("c://someDir//src//Generated//", $"{Path.Combine(Path.GetFullPath("c://someDir"), "src", Constants.DefaultGeneratedCodeFolderName)}\\"); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/ConfigurationTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/ConfigurationTests.cs new file mode 100644 index 0000000000..bbd279342e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/ConfigurationTests.cs @@ -0,0 +1,227 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Generator.CSharp.Expressions; +using Moq; +using NUnit.Framework; +using NUnit.Framework.Internal; +using static Microsoft.Generator.CSharp.Expressions.ExtensibleSnippets; + +namespace Microsoft.Generator.CSharp.Tests +{ + // Tests for the Configuration class + public class ConfigurationTests + { +#pragma warning disable CS8602 // Dereference of a possibly null reference. + private readonly string _mocksFolder = "./mocks"; + + // Validates that the configuration is initialized correctly given input + [Test] + public void TestInitialize() + { + // mock api types + var mockApiTypes = new Mock() + { + CallBase = true + }; + + var mockExtensibleSnippets = new Mock() + { + CallBase = true + }; + + mockApiTypes.SetupGet(p => p.ChangeTrackingListType).Returns(typeof(IList<>)); + mockApiTypes.SetupGet(p => p.ChangeTrackingDictionaryType).Returns(typeof(IDictionary)); + mockApiTypes.SetupGet(p => p.EndPointSampleValue).Returns("Sample"); + + var modelSnippets = new Mock().Object; + mockExtensibleSnippets.SetupGet(p => p.Model).Returns(modelSnippets); + + string ns = "sample.namespace"; + string? unknownStringProperty = "unknownPropertyValue"; + bool? unknownBoolProp = false; + + var configuration = Configuration.Load(_mocksFolder); + + var parsedNs = configuration.Namespace; + + Assert.AreEqual(ns, parsedNs); + // get the unknown property from the configuration + var additionalConfigOptions = configuration.AdditionalConfigOptions; + Assert.IsNotNull(additionalConfigOptions); + Assert.IsTrue(additionalConfigOptions!.ContainsKey("unknown-string-property")); + Assert.IsTrue(additionalConfigOptions.ContainsKey("unknown-bool-property")); + + string unknownStringValue = additionalConfigOptions["unknown-string-property"].ToObjectFromJson()!; + Assert.AreEqual(unknownStringProperty, unknownStringValue); + + bool unknownBoolValue = additionalConfigOptions["unknown-bool-property"].ToObjectFromJson(); + Assert.AreEqual(unknownBoolProp, unknownBoolValue); + } + + // Validates that an exception is thrown when no configuration file is found + [Test] + public void TestInitialize_NoFileFound() + { + var configFilePath = Path.Combine(_mocksFolder, "unknown_file.out"); + Assert.Throws(() => Configuration.Load(configFilePath)); + } + + // Validates that the output folder is parsed correctly from the configuration + [TestCaseSource("ParseConfigOutputFolderTestCases")] + public void TestParseConfig_OutputFolder(string mockJson, bool throwsError) + { + + var expected = Path.GetFullPath(_mocksFolder); + + if (throwsError) + { + Assert.Throws(() => Configuration.Load(string.Empty)); + return; + } + + var configuration = Configuration.Load(_mocksFolder, mockJson); + + Assert.AreEqual(expected, configuration.OutputDirectory); + } + + // Validates that the LibraryName field is parsed correctly from the configuration + [TestCaseSource("ParseConfigLibraryNameTestCases")] + public void TestParseConfig_LibraryName(string mockJson, bool throwsError) + { + + if (throwsError) + { + Assert.Throws(() => Configuration.Load(string.Empty, mockJson)); + return; + } + + var configuration = Configuration.Load(string.Empty, mockJson); + var library = configuration.LibraryName; + var expected = "libraryName"; + + Assert.AreEqual(expected, library); + } + + // Validates that the namespace field is parsed correctly from the configuration + [TestCaseSource("ParseConfigNamespaceTestCases")] + public void TestParseConfig_Namespace(string mockJson, bool throwsError) + { + if (throwsError) + { + Assert.Throws(() => Configuration.Load(string.Empty, mockJson)); + return; + } + + var configuration = Configuration.Load(string.Empty, mockJson); + var ns = configuration.Namespace; + var expected = "namespace"; + + Assert.AreEqual(expected, ns); + } + + // Validates that the output folder is parsed correctly from the configuration + [TestCaseSource("ParseConfigUseModelNamespaceTestCases")] + public void TestParseConfig_UseModelNamespace(string mockJson, bool expected) + { + var configuration = Configuration.Load(string.Empty, mockJson); + var useModelNs = configuration.UseModelNamespace; + + Assert.AreEqual(expected, useModelNs); + } + + // Validates that additional configuration options are parsed correctly + [Test] + public void TestParseConfig_AdditionalConfigOptions() + { + var mockJson = @"{ + ""output-folder"": ""outputFolder"", + ""library-name"": ""libraryName"", + ""namespace"": ""namespace"", + ""unknown-string-property"": ""unknownPropertyValue"", + ""unknown-bool-property"": true + }"; + + var configuration = Configuration.Load(string.Empty, mockJson); + + var additionalConfigOptions = configuration.AdditionalConfigOptions; + Assert.IsNotNull(additionalConfigOptions); + Assert.IsTrue(additionalConfigOptions!.ContainsKey("unknown-string-property")); + Assert.IsTrue(additionalConfigOptions.ContainsKey("unknown-bool-property")); + + string unknownStringValue = additionalConfigOptions["unknown-string-property"].ToObjectFromJson()!; + Assert.AreEqual("unknownPropertyValue", unknownStringValue); + + bool unknownBoolValue = additionalConfigOptions["unknown-bool-property"].ToObjectFromJson(); + Assert.AreEqual(true, unknownBoolValue); + } + + public static IEnumerable ParseConfigOutputFolderTestCases + { + get + { + yield return new TestCaseData(@"{ + ""output-folder"": ""outputFolder"", + ""library-name"": ""libraryName"", + ""namespace"": ""namespace"" + }", false); + yield return new TestCaseData(@"{ + ""library-name"": ""libraryName"", + ""namespace"": ""namespace"" + }", true); + } + } + + public static IEnumerable ParseConfigLibraryNameTestCases + { + get + { + yield return new TestCaseData(@"{ + ""output-folder"": ""outputFolder"", + ""library-name"": ""libraryName"", + ""namespace"": ""namespace"" + }", false); + yield return new TestCaseData(@"{ + ""output-folder"": ""outputFolder"" + }", true); + } + } + + public static IEnumerable ParseConfigNamespaceTestCases + { + get + { + yield return new TestCaseData(@"{ + ""output-folder"": ""outputFolder"", + ""library-name"": ""libraryName"", + ""namespace"": ""namespace"" + }", false); + yield return new TestCaseData(@"{ + ""output-folder"": ""outputFolder"", + ""library-name"": ""libraryName"" + }", true); + } + } + + public static IEnumerable ParseConfigUseModelNamespaceTestCases + { + get + { + yield return new TestCaseData(@"{ + ""output-folder"": ""outputFolder"", + ""library-name"": ""libraryName"", + ""namespace"": ""namespace"", + ""use-model-namespace"": true + }", true); + yield return new TestCaseData(@"{ + ""output-folder"": ""outputFolder"", + ""library-name"": ""libraryName"", + ""namespace"": ""namespace"" + }", true); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Directory.Build.props b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Directory.Build.props new file mode 100644 index 0000000000..6803b52fc2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Directory.Build.props @@ -0,0 +1,7 @@ + + + true + + + + diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/KnownParametersTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/KnownParametersTests.cs new file mode 100644 index 0000000000..eae35ff5ed --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/KnownParametersTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Moq; +using NUnit.Framework; + +namespace Microsoft.Generator.CSharp.Tests +{ +#pragma warning disable CS8618 + internal class KnownParametersTests + { + private Mock _factory; + private KnownParameters _knownParameters; +#pragma warning restore CS8618 + + [SetUp] + public void Setup() + { + _factory = new Mock(); + _knownParameters = new KnownParameters(_factory.Object); + } + + [TestCase(false)] + [TestCase(true)] + public void TestTokenAuth(bool notImplemented) + { + if (notImplemented) + { + _factory.Setup(x => x.TokenCredentialType()).Throws(); + Assert.That(() => _knownParameters.TokenAuth, Throws.Exception.TypeOf()); + } + else + { + _factory.Setup(x => x.TokenCredentialType()).Returns(new CSharpType(typeof(int))); + var result = _knownParameters.TokenAuth; + Assert.IsNotNull(result); + Assert.IsNotNull(result.Type); + Assert.IsTrue(result.Type.Equals(new CSharpType(typeof(int)))); + } + } + + [TestCase(false)] + [TestCase(true)] + public void TestMatchConditionsParameter(bool notImplemented) + { + if (notImplemented) + { + _factory.Setup(x => x.MatchConditionsType()).Throws(); + Assert.That(() => _knownParameters.MatchConditionsParameter, Throws.Exception.TypeOf()); + } + else + { + _factory.Setup(x => x.RequestConditionsType()).Returns(new CSharpType(typeof(int))); + _factory.Setup(x => x.MatchConditionsType()).Returns(new CSharpType(typeof(int))); + var result = _knownParameters.MatchConditionsParameter; + Assert.IsNotNull(result); + Assert.IsNotNull(result.Type); + Assert.IsTrue(result.Type.Equals(new CSharpType(typeof(int)))); + } + } + + [TestCase(false)] + [TestCase(true)] + public void TestRequestConditionsParameter(bool notImplemented) + { + if (notImplemented) + { + _factory.Setup(x => x.RequestConditionsType()).Throws(); + Assert.That(() => _knownParameters.RequestConditionsParameter, Throws.Exception.TypeOf()); + } + else + { + _factory.Setup(x => x.RequestConditionsType()).Returns(new CSharpType(typeof(int))); + _factory.Setup(x => x.MatchConditionsType()).Returns(new CSharpType(typeof(int))); + var result = _knownParameters.RequestConditionsParameter; + Assert.IsNotNull(result); + Assert.IsNotNull(result.Type); + Assert.IsTrue(result.Type.Equals(new CSharpType(typeof(int)))); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/KnownValueExpressionsTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/KnownValueExpressionsTests.cs new file mode 100644 index 0000000000..491040a0b3 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/KnownValueExpressionsTests.cs @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Generator.CSharp.Expressions; +using NUnit.Framework; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class KnownValueExpressionsTests + { + [Test] + public void BinaryOperatorExpressionWithOrOperator() + { + var left = new ValueExpression(); + var right = new ValueExpression(); + var boolExpression = new BoolExpression(left); + + var result = boolExpression.Or(right); + + Assert.AreEqual(new BinaryOperatorExpression(" || ", boolExpression, right), result.Untyped); + } + + [Test] + public void BinaryOperatorExpressionWithAndOperator() + { + var left = new ValueExpression(); + var right = new ValueExpression(); + var boolExpression = new BoolExpression(left); + + var result = boolExpression.And(right); + + Assert.AreEqual(new BinaryOperatorExpression(" && ", boolExpression, right), result.Untyped); + } + + [TestCase(typeof(int))] + [TestCase(typeof(long))] + [TestCase(typeof(float))] + [TestCase(typeof(double))] + [TestCase(typeof(bool))] + [TestCase(typeof(string))] + [TestCase(typeof(Guid))] + [TestCase(typeof(IList<>))] + [TestCase(typeof(IList))] + [TestCase(typeof(IDictionary<,>))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>))] + public void ListExpression(Type T) + { + var itemType = new CSharpType(T); + var untypedValue = new ValueExpression(); + + var listExpression = new ListExpression(itemType, untypedValue); + + Assert.AreEqual(itemType, listExpression.ItemType); + Assert.AreEqual(untypedValue, listExpression.Untyped); + } + + [Test] + public void ListExpressionAddItem() + { + var item = new ValueExpression(); + var listExpression = new ListExpression(new CSharpType(typeof(int)), new ValueExpression()); + + var result = listExpression.Add(item); + + var expectedStatement = new InvokeInstanceMethodStatement(listExpression.Untyped, "Add", item); + + Assert.AreEqual(expectedStatement.ToString(), result.ToString()); + } + + [TestCase(typeof(int), typeof(string))] + [TestCase(typeof(int), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(int))] + [TestCase(typeof(int), typeof(IList))] + [TestCase(typeof(IList), typeof(int))] + [TestCase(typeof(IList), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(IList))] + [TestCase(typeof(IList), typeof(IList))] + [TestCase(typeof(IList), typeof(ICollection))] + [TestCase(typeof(Tuple<>), typeof(Tuple<,>))] + [TestCase(typeof(IDictionary), typeof(Dictionary<,>))] + [TestCase(typeof(IDictionary), typeof(Dictionary))] + [TestCase(typeof(IDictionary), typeof(Dictionary))] + [TestCase(typeof(IDictionary), typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>), typeof(IDictionary, IDictionary>))] + public void DictionaryExpression(Type t1, Type t2) + { + var keyType = new CSharpType(t1); + var valueType = new CSharpType(t2); + var untypedValue = new ValueExpression(); + + var dictionaryExpression = new DictionaryExpression(t1, t2, untypedValue); + + Assert.AreEqual(keyType, dictionaryExpression.KeyType); + Assert.AreEqual(valueType, dictionaryExpression.ValueType); + } + + + [Test] + public void DictionaryExpressionAddItems() + { + var keyType = new CSharpType(typeof(int)); + var valueType = new CSharpType(typeof(string)); + var dictionaryExpression = new DictionaryExpression(keyType, valueType, new ValueExpression()); + + var key = new ValueExpression(); + var value = new ValueExpression(); + var result = dictionaryExpression.Add(key, value); + + var expectedStatement = new InvokeInstanceMethodStatement(dictionaryExpression.Untyped, nameof(Dictionary.Add), key, value); + + Assert.AreEqual(expectedStatement.ToString(), result.ToString()); + } + + [TestCase(typeof(int), typeof(string))] + [TestCase(typeof(int), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(int))] + [TestCase(typeof(int), typeof(IList))] + [TestCase(typeof(IList), typeof(int))] + [TestCase(typeof(IList), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(IList))] + [TestCase(typeof(IList), typeof(IList))] + [TestCase(typeof(IList), typeof(ICollection))] + [TestCase(typeof(Tuple<>), typeof(Tuple<,>))] + [TestCase(typeof(IDictionary), typeof(Dictionary<,>))] + [TestCase(typeof(IDictionary), typeof(Dictionary))] + [TestCase(typeof(IDictionary), typeof(Dictionary))] + [TestCase(typeof(IDictionary), typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>), typeof(IDictionary, IDictionary>))] + public void KeyValuePairExpression(Type t1, Type t2) + { + var keyType = new CSharpType(t1); + var valueType = new CSharpType(t2); + var untypedValue = new ValueExpression(); + + var keyValuePairExpression = new KeyValuePairExpression(keyType, valueType, untypedValue); + var expectedKey = new TypedMemberExpression(keyValuePairExpression.Untyped, nameof(KeyValuePair.Key), keyType); + var expectedValue = new TypedMemberExpression(keyValuePairExpression.Untyped, nameof(KeyValuePair.Value), valueType); + + Assert.AreEqual(expectedKey, keyValuePairExpression.Key); + Assert.AreEqual(expectedValue, keyValuePairExpression.Value); + Assert.AreEqual(keyType, keyValuePairExpression.KeyType); + Assert.AreEqual(valueType, keyValuePairExpression.ValueType); + } + + [Test] + public void EnumerableExpressionWithAnyMethodCall() + { + var itemType = new CSharpType(typeof(int)); + var untypedValue = new ValueExpression(); + var enumerableExpression = new EnumerableExpression(itemType, untypedValue); + + var result = enumerableExpression.Any(); + + var expectedExpression = new BoolExpression( + new InvokeStaticMethodExpression( + typeof(Enumerable), + nameof(Enumerable.Any), + new[] { untypedValue }, + CallAsExtension: true + ) + ); + + Assert.AreEqual(expectedExpression.Untyped.ToString(), result.Untyped.ToString()); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Microsoft.Generator.CSharp.Tests.csproj b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Microsoft.Generator.CSharp.Tests.csproj new file mode 100644 index 0000000000..8aac1c11e3 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Microsoft.Generator.CSharp.Tests.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + false + true + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/StatementTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/StatementTests.cs new file mode 100644 index 0000000000..ccafae22f4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/StatementTests.cs @@ -0,0 +1,306 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Generator.CSharp.Expressions; +using NUnit.Framework; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class StatementTests + { + [Test] + public void AssignValueIfNullStatement() + { + var toValue = new ValueExpression(); + var fromValue = new ValueExpression(); + + var assignStatement = new AssignValueIfNullStatement(toValue, fromValue); + + Assert.NotNull(assignStatement); + Assert.AreEqual(toValue, assignStatement.To); + Assert.AreEqual(fromValue, assignStatement.From); + } + + [Test] + public void AssignValueStatement() + { + var toValue = new ValueExpression(); + var fromValue = new ValueExpression(); + + var assignStatement = new AssignValueStatement(toValue, fromValue); + + Assert.NotNull(assignStatement); + Assert.AreEqual(toValue, assignStatement.To); + Assert.AreEqual(fromValue, assignStatement.From); + } + + [Test] + public void CreateForStatement() + { + var assignment = new AssignmentExpression(new VariableReference(new CSharpType(typeof(BinaryData)), "responseParamName"), new ValueExpression()); + var condition = new BoolExpression(BoolExpression.True); + var increment = new ValueExpression(); + var forStatement = new ForStatement(assignment, condition, increment); + + Assert.NotNull(forStatement); + Assert.IsEmpty(forStatement.Body); + } + + [Test] + public void ForStatementWithAddMethod() + { + var assignment = new AssignmentExpression(new VariableReference(new CSharpType(typeof(BinaryData)), "responseParamName"), new ValueExpression()); + var condition = new BoolExpression(BoolExpression.True); + var increment = new ValueExpression(); + var forStatement = new ForStatement(assignment, condition, increment); + var statementToAdd = new MethodBodyStatement(); + + forStatement.Add(statementToAdd); + + Assert.NotNull(forStatement.Body); + Assert.IsNotEmpty(forStatement.Body); + Assert.AreEqual(statementToAdd, forStatement.Body[0]); + } + + [Test] + public void CreateForeachStatement() + { + var itemType = new CSharpType(typeof(int)); + var itemName = "item"; + var enumerable = new ValueExpression(); + + var foreachStatement = new ForeachStatement(itemType, itemName, enumerable, isAsync: false, out var itemReference); + + Assert.NotNull(foreachStatement); + Assert.AreEqual(itemType, foreachStatement.ItemType); + Assert.AreEqual(itemName, foreachStatement.Item.RequestedName); + Assert.AreEqual(enumerable, foreachStatement.Enumerable); + Assert.IsFalse(foreachStatement.IsAsync); + Assert.NotNull(itemReference); + Assert.AreEqual(itemType, itemReference.Type); + Assert.AreEqual(itemName, itemReference.Declaration.RequestedName); + } + + [Test] + public void ForeachStatementWithAddMethod() + { + var foreachStatement = new ForeachStatement(new CSharpType(typeof(int)), "item", new ValueExpression(), isAsync: false, out var itemReference); + var statementToAdd = new MethodBodyStatement(); + + foreachStatement.Add(statementToAdd); + + Assert.NotNull(foreachStatement.Body); + Assert.IsNotEmpty(foreachStatement.Body); + Assert.IsTrue(foreachStatement.Body.Any(s => s == statementToAdd)); + } + + [Test] + public void IfStatementWithBoolExpression() + { + var condition = new BoolExpression(BoolExpression.True); + var ifStatement = new IfStatement(condition); + + Assert.NotNull(ifStatement); + Assert.AreEqual(condition, ifStatement.Condition); + Assert.NotNull(ifStatement.Body); + } + + [Test] + public void IfStatementWithAddMethod() + { + var ifStatement = new IfStatement(BoolExpression.True); + var statementToAdd = new MethodBodyStatement(); + + ifStatement.Add(statementToAdd); + + Assert.NotNull(ifStatement.Body); + Assert.IsInstanceOf(ifStatement.Body); + Assert.IsTrue(((MethodBodyStatements)ifStatement.Body).Statements.Any(s => s == statementToAdd)); + } + + [Test] + public void IfStatementWithDefaultOptions() + { + var condition = new BoolExpression(BoolExpression.True); + var ifStatement = new IfStatement(condition); + + Assert.IsFalse(ifStatement.Inline); + Assert.IsTrue(ifStatement.AddBraces); + } + + [Test] + public void IfStatementInlineOptionTrue() + { + var condition = new BoolExpression(BoolExpression.True); + var ifStatement = new IfStatement(condition, Inline: true); + + Assert.IsTrue(ifStatement.Inline); + } + + [Test] + public void IfStatementAddBracesOptionFalse() + { + var condition = new BoolExpression(BoolExpression.True); + var ifStatement = new IfStatement(condition, AddBraces: false); + + Assert.IsFalse(ifStatement.AddBraces); + } + + [Test] + public void IfElseStatementWithIfAndElse() + { + var condition = new BoolExpression(BoolExpression.True); + var elseStatement = new MethodBodyStatement(); + + var ifElseStatement = new IfElseStatement(new IfStatement(condition), elseStatement); + + Assert.NotNull(ifElseStatement); + Assert.NotNull(ifElseStatement.If); + Assert.AreEqual(condition, ifElseStatement.If.Condition); + Assert.AreEqual(elseStatement, ifElseStatement.Else); + } + + [Test] + public void IfElseStatementWithConditionAndStatements() + { + var condition = new BoolExpression(BoolExpression.True); + var ifStatement = new MethodBodyStatement(); + var elseStatement = new MethodBodyStatement(); + + var ifElseStatement = new IfElseStatement(condition, ifStatement, elseStatement); + + Assert.NotNull(ifElseStatement); + Assert.NotNull(ifElseStatement.If); + Assert.AreEqual(condition, ifElseStatement.If.Condition); + Assert.AreEqual(elseStatement, ifElseStatement.Else); + } + + [Test] + public void SwitchStatementWithSingleCase() + { + var matchExpression = new ValueExpression(); + var switchStatement = new SwitchStatement(matchExpression); + + var caseStatement = new MethodBodyStatement(); + var switchCase = new SwitchCase(new ValueExpression(), caseStatement); + + switchStatement.Add(switchCase); + + Assert.AreEqual(1, switchStatement.Cases.Count); + Assert.AreEqual(switchCase, switchStatement.Cases[0]); + } + + [Test] + public void SwitchStatementWithMultipleCases() + { + var matchExpression = new ValueExpression(); + var switchStatement = new SwitchStatement(matchExpression); + + var caseStatements = new List + { + new SwitchCase(new ValueExpression(), new MethodBodyStatement()), + new SwitchCase(new ValueExpression(), new MethodBodyStatement()) + }; + + foreach (var switchCase in caseStatements) + { + switchStatement.Add(switchCase); + } + + CollectionAssert.AreEqual(caseStatements, switchStatement.Cases); + } + + [Test] + public void SwitchStatementEnumeratingCases() + { + var matchExpression = new ValueExpression(); + var switchStatement = new SwitchStatement(matchExpression); + + var caseStatements = new List + { + new SwitchCase(new ValueExpression(), new MethodBodyStatement()), + new SwitchCase(new ValueExpression(), new MethodBodyStatement()) + }; + + foreach (var switchCase in caseStatements) + { + switchStatement.Add(switchCase); + } + + var enumeratedCases = new List(); + foreach (var caseItem in switchStatement) + { + enumeratedCases.Add(caseItem); + } + + CollectionAssert.AreEqual(caseStatements, enumeratedCases); + } + + [Test] + public void TryCatchFinallyStatementWithTryOnly() + { + var tryStatement = new MethodBodyStatement(); + var tryCatchFinally = new TryCatchFinallyStatement(tryStatement); + + Assert.AreEqual(tryStatement, tryCatchFinally.Try); + Assert.AreEqual(0, tryCatchFinally.Catches.Count); + Assert.IsNull(tryCatchFinally.Finally); + } + + [Test] + public void TryCatchFinallyStatementWithTryAndCatch() + { + var tryStatement = new MethodBodyStatement(); + var catchStatement = new CatchStatement(null, new MethodBodyStatement()); + var tryCatchFinally = new TryCatchFinallyStatement(tryStatement, catchStatement, null); + + Assert.AreEqual(tryStatement, tryCatchFinally.Try); + Assert.AreEqual(1, tryCatchFinally.Catches.Count); + Assert.AreEqual(catchStatement, tryCatchFinally.Catches[0]); + Assert.IsNull(tryCatchFinally.Finally); + } + + [Test] + public void TryCatchFinallyStatementWithTryCatchAndFinally() + { + var tryStatement = new MethodBodyStatement(); + var catchStatement = new CatchStatement(null, new MethodBodyStatement()); + var finallyStatement = new MethodBodyStatement(); + var tryCatchFinally = new TryCatchFinallyStatement(tryStatement, catchStatement, finallyStatement); + + Assert.AreEqual(tryStatement, tryCatchFinally.Try); + Assert.AreEqual(1, tryCatchFinally.Catches.Count); + Assert.AreEqual(catchStatement, tryCatchFinally.Catches[0]); + Assert.AreEqual(finallyStatement, tryCatchFinally.Finally); + } + + [Test] + public void TryCatchFinallyStatementWithMultipleCatches() + { + var tryStatement = new MethodBodyStatement(); + var catchStatements = new[] + { + new CatchStatement(null, new MethodBodyStatement()), + new CatchStatement(null, new MethodBodyStatement()) + }; + var finallyStatement = new MethodBodyStatement(); + var tryCatchFinally = new TryCatchFinallyStatement(tryStatement, catchStatements, finallyStatement); + + Assert.AreEqual(tryStatement, tryCatchFinally.Try); + CollectionAssert.AreEqual(catchStatements, tryCatchFinally.Catches); + Assert.AreEqual(finallyStatement, tryCatchFinally.Finally); + } + + [Test] + public void UnaryOperatorStatementWithValidExpression() + { + var operatorExpression = new UnaryOperatorExpression("-", new ValueExpression(), true); + var unaryOperatorStatement = new UnaryOperatorStatement(operatorExpression); + + Assert.AreEqual(operatorExpression, unaryOperatorStatement.Expression); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/StringExtensionsTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/StringExtensionsTests.cs new file mode 100644 index 0000000000..397e50d512 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/StringExtensionsTests.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using NUnit.Framework; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class StringExtensionsTests + { + + [TestCase("abstract", true)] + [TestCase("add", true)] + [TestCase("alias", true)] + [TestCase("as", true)] + [TestCase("ascending", true)] + [TestCase("async", true)] + [TestCase("await", true)] + [TestCase("base", true)] + [TestCase("bool", true)] + [TestCase("break", true)] + [TestCase("by", true)] + [TestCase("byte", true)] + [TestCase("case", true)] + [TestCase("catch", true)] + [TestCase("char", true)] + [TestCase("checked", true)] + [TestCase("class", true)] + [TestCase("const", true)] + [TestCase("continue", true)] + [TestCase("decimal", true)] + [TestCase("default", true)] + [TestCase("delegate", true)] + [TestCase("descending", true)] + [TestCase("do", true)] + [TestCase("double", true)] + [TestCase("dynamic", false)] + [TestCase("else", true)] + [TestCase("enum", true)] + [TestCase("equals", true)] + [TestCase("event", true)] + [TestCase("explicit", true)] + [TestCase("extern", true)] + [TestCase("false", true)] + [TestCase("finally", true)] + [TestCase("fixed", true)] + [TestCase("float", true)] + [TestCase("for", true)] + [TestCase("foreach", true)] + [TestCase("from", true)] + [TestCase("get", true)] + [TestCase("global", true)] + [TestCase("goto", true)] + [TestCase("if", true)] + [TestCase("implicit", true)] + [TestCase("in", true)] + [TestCase("int", true)] + [TestCase("interface", true)] + [TestCase("internal", true)] + [TestCase("into", true)] + [TestCase("is", true)] + [TestCase("join", true)] + [TestCase("let", true)] + [TestCase("lock", true)] + [TestCase("long", true)] + [TestCase("nameof", true)] + [TestCase("namespace", true)] + [TestCase("new", true)] + [TestCase("null", true)] + [TestCase("object", true)] + [TestCase("on", true)] + [TestCase("operator", true)] + [TestCase("out", true)] + [TestCase("override", true)] + [TestCase("params", true)] + [TestCase("partial", true)] + [TestCase("private", true)] + [TestCase("protected", true)] + [TestCase("public", true)] + [TestCase("readonly", true)] + [TestCase("ref", true)] + [TestCase("remove", true)] + [TestCase("return", true)] + [TestCase("sbyte", true)] + [TestCase("sealed", true)] + [TestCase("set", true)] + [TestCase("short", true)] + [TestCase("sizeof", true)] + [TestCase("stackalloc", true)] + [TestCase("static", true)] + [TestCase("string", true)] + [TestCase("struct", true)] + [TestCase("switch", true)] + [TestCase("this", true)] + [TestCase("throw", true)] + [TestCase("true", true)] + [TestCase("try", true)] + [TestCase("typeof", true)] + [TestCase("uint", true)] + [TestCase("ulong", true)] + [TestCase("unchecked", true)] + [TestCase("unmanaged", true)] + [TestCase("unsafe", true)] + [TestCase("ushort", true)] + [TestCase("using", true)] + [TestCase("var", true)] + [TestCase("virtual", true)] + [TestCase("void", true)] + [TestCase("volatile", true)] + [TestCase("when", true)] + [TestCase("where", true)] + [TestCase("while", true)] + [TestCase("yield", true)] + public void TestIsCSharpKeyword(string name, bool isKeyword) + { + var result = StringExtensions.IsCSharpKeyword(name); + Assert.AreEqual(isKeyword, result); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TypeFactoryTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TypeFactoryTests.cs new file mode 100644 index 0000000000..43251fb146 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TypeFactoryTests.cs @@ -0,0 +1,215 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Generator.CSharp.Expressions; +using Microsoft.Generator.CSharp.Input; +using NUnit.Framework; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class TypeFactoryTests + { + private CustomTypeFactory? customFactory; + + [SetUp] + public void Setup() + { + customFactory = new CustomTypeFactory(); + } + + /// + /// Validates that the factory method for creating a based on an input type works as expected. + /// + /// The input type to convert. + /// The expected . + [TestCaseSource("CreateTypeTestCases")] + public void TestCreateType(InputType inputType, CSharpType? expectedType, bool expectedError) + { + if (expectedError) + { + Assert.Throws(() => customFactory?.CreateType(inputType)); + return; + } + else + { + Assert.IsNotNull(inputType); + Assert.IsNotNull(expectedType); + + var actual = customFactory?.CreateType(inputType); + + Assert.IsNotNull(actual); + + expectedType!.Equals(actual!); + } + } + + /// + /// Validates that the factory method for creating a based on an input operation works as expected. + /// + /// The input operation to convert. + /// The expected . + [TestCaseSource("CreateMethodTestCases")] + public void TestCreateMethod(InputOperation operation, Method expectedMethod) + { + Assert.IsNotNull(operation); + Assert.IsNotNull(expectedMethod); + + var actual = customFactory?.CreateMethod(operation); + + Assert.IsNotNull(actual); + string actualType = actual!.Kind; + Assert.AreEqual(expectedMethod.Kind, actualType); + } + + public static IEnumerable CreateTypeTestCases + { + get + { + yield return new TestCaseData(new InputList("sampleType", new InputPrimitiveType(InputTypeKind.Boolean, false), false, false), new CSharpType(typeof(InputList), isNullable: false), false); + yield return new TestCaseData(new InputDictionary("sampleType", new InputPrimitiveType(InputTypeKind.String, false), new InputPrimitiveType(InputTypeKind.Int32, false), false), new CSharpType(typeof(InputDictionary), isNullable: false), false); + yield return new TestCaseData(new InputPrimitiveType(InputTypeKind.String, false), new CSharpType(typeof(InputPrimitiveType), isNullable: false), false); + yield return new TestCaseData(new InputLiteralType("literalType", new InputPrimitiveType(InputTypeKind.String, false), "literal", false), null, true); + } + } + + public static IEnumerable CreateMethodTestCases + { + get + { + yield return new TestCaseData(new InputOperation(), + new Method(new MethodSignature(string.Empty, $"{string.Empty}", null, MethodSignatureModifiers.Public, null, null, Array.Empty()), + Array.Empty(), + "default" + )); + yield return new TestCaseData(new InputOperation(name: string.Empty, + resourceName: null, + summary: null, + deprecated: null, + description: string.Empty, + accessibility: null, + parameters: Array.Empty(), + responses: Array.Empty(), + httpMethod: string.Empty, + requestBodyMediaType: BodyMediaType.None, + uri: string.Empty, + path: string.Empty, + externalDocsUrl: null, + requestMediaTypes: Array.Empty(), + bufferResponse: false, + longRunning: new OperationLongRunning(), + paging: null, + generateProtocolMethod: true, + generateConvenienceMethod: false), + new Method(new MethodSignature(string.Empty, $"{string.Empty}", null, MethodSignatureModifiers.Public, null, null, Array.Empty()), + Array.Empty(), + "longRunning" + )); + yield return new TestCaseData(new InputOperation(name: string.Empty, + resourceName: null, + summary: null, + deprecated: null, + description: string.Empty, + accessibility: null, + parameters: Array.Empty(), + responses: Array.Empty(), + httpMethod: string.Empty, + requestBodyMediaType: BodyMediaType.None, + uri: string.Empty, + path: string.Empty, + externalDocsUrl: null, + requestMediaTypes: Array.Empty(), + bufferResponse: false, + longRunning: null, + paging: new OperationPaging(), + generateProtocolMethod: true, + generateConvenienceMethod: false), + new Method(new MethodSignature(string.Empty, $"{string.Empty}", null, MethodSignatureModifiers.Public, null, null, Array.Empty()), + Array.Empty(), + "paging" + )); + } + } + + internal class CustomTypeFactory : TypeFactory + { + public override CSharpType RequestConditionsType() + { + throw new NotImplementedException(); + } + + public override CSharpType TokenCredentialType() + { + throw new NotImplementedException(); + } + + public override CSharpType MatchConditionsType() + { + throw new NotImplementedException(); + } + + public override CSharpType PageResponseType() + { + throw new NotImplementedException(); + } + + public override CSharpType CreateType(InputType input) + { + switch (input) + { + case InputList: + return new CSharpType(typeof(InputList), isNullable: false); + case InputDictionary: + return new CSharpType(typeof(InputDictionary), isNullable: false); + case InputPrimitiveType: + return new CSharpType(typeof(InputPrimitiveType), isNullable: false); + default: + throw new NotImplementedException("Unknown input type"); + } + } + + public override Method CreateMethod(InputOperation operation, bool returnProtocol = true) + { + var methodType = GetMethodType(operation); + switch (methodType) + { + case "default": + return new Method + ( + new MethodSignature(operation.Name, $"{operation?.Summary}", null, MethodSignatureModifiers.Public, null, null, new Parameter[0]), + new MethodBodyStatement[0], + methodType + ); + case "longRunning": + return new Method + ( + new MethodSignature(operation.Name, $"{operation?.Summary}", null, MethodSignatureModifiers.Public, null, null, new Parameter[0]), + new MethodBodyStatement[0], + methodType + ); + case "paging": + return new Method + ( + new MethodSignature(operation.Name, $"{operation?.Summary}", null, MethodSignatureModifiers.Public, null, null, new Parameter[0]), + new MethodBodyStatement[0], + methodType + ); + default: + throw new Exception($"Unknown method type {methodType}"); + } + } + + private static string GetMethodType(InputOperation operation) + { + var defaultMethodType = "default"; + + if (operation.LongRunning is not null) + return "longRunning"; + if (operation.Paging is not null) + return "paging"; + return defaultMethodType; + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/ValueExpressionTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/ValueExpressionTests.cs new file mode 100644 index 0000000000..7532eaaa6a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/ValueExpressionTests.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.Generator.CSharp.Expressions; +using NUnit.Framework; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class ValueExpressionTests + { + [TestCase(typeof(int))] + [TestCase(typeof(long))] + [TestCase(typeof(float))] + [TestCase(typeof(double))] + [TestCase(typeof(bool))] + [TestCase(typeof(string))] + [TestCase(typeof(Guid))] + [TestCase(typeof(IList<>))] + [TestCase(typeof(IList))] + [TestCase(typeof(IDictionary<,>))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>))] + public void SystemTypeOperatorTestEqual(Type t) + { + ValueExpression result = t; + var expected = new TypeReference(t); + + Assert.AreEqual(expected, result); + } + + [TestCase(typeof(int), typeof(string))] + [TestCase(typeof(int), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(int))] + [TestCase(typeof(int), typeof(IList))] + [TestCase(typeof(IList), typeof(int))] + [TestCase(typeof(IList), typeof(IList<>))] + [TestCase(typeof(IList<>), typeof(IList))] + [TestCase(typeof(IList), typeof(IList))] + [TestCase(typeof(IList), typeof(ICollection))] + [TestCase(typeof(Tuple<>), typeof(Tuple<,>))] + [TestCase(typeof(IDictionary), typeof(Dictionary<,>))] + [TestCase(typeof(IDictionary), typeof(Dictionary))] + [TestCase(typeof(IDictionary), typeof(Dictionary))] + [TestCase(typeof(IDictionary), typeof(IDictionary))] + [TestCase(typeof(IDictionary, IDictionary>), typeof(IDictionary, IDictionary>))] + public void SystemTypeOperatorTestNotEqual(Type t1, Type t2) + { + ValueExpression result = t1; + var expected = new TypeReference(t2); + + Assert.AreNotEqual(expected, result); + } + + [TestCaseSource(nameof(CSharpTypeOperatorEqualsTestCases))] + public void CSharpTypeOperatorTestEqual(CSharpType t) + { + ValueExpression result = t; + var expected = new TypeReference(t); + + Assert.AreEqual(expected, result); + } + + [TestCaseSource(nameof(CSharpTypeOperatorNotEqualTestCases))] + public void CSharpTypeOperatorTestNotEqual(CSharpType t1, CSharpType t2) + { + ValueExpression result = t1; + var expected = new TypeReference(t2); + + Assert.AreNotEqual(expected, result); + } + + public static IEnumerable CSharpTypeOperatorEqualsTestCases + { + get + { + yield return new TestCaseData(new CSharpType(typeof(int))); + yield return new TestCaseData(new CSharpType(typeof(string))); + yield return new TestCaseData(new CSharpType(typeof(bool))); + yield return new TestCaseData(new CSharpType(typeof(IList<>))); + } + } + + public static IEnumerable CSharpTypeOperatorNotEqualTestCases + { + get + { + yield return new TestCaseData(new CSharpType(typeof(int)), new CSharpType(typeof(bool))); + yield return new TestCaseData(new CSharpType(typeof(string)), new CSharpType(typeof(int))); + yield return new TestCaseData(new CSharpType(typeof(bool)), new CSharpType(typeof(string))); + yield return new TestCaseData(new CSharpType(typeof(IList<>)), new CSharpType(typeof(IDictionary<,>))); + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/WorkspaceMetadataReferenceResolverTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/WorkspaceMetadataReferenceResolverTests.cs new file mode 100644 index 0000000000..a8c9def891 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/WorkspaceMetadataReferenceResolverTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Moq; +using NUnit.Framework; + +namespace Microsoft.Generator.CSharp.Tests +{ + internal class WorkspaceMetadataReferenceResolverTests + { + // This test validates that the probing paths are correctly set in the resolver. + [Test] + public void WorkspaceMetadataReferenceResolverCtor() + { + var resolver = new Mock() { CallBase = true }; + var probingPaths = resolver.Object.GetType()?.BaseType?.GetField("_probingPaths", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(resolver.Object) as HashSet; + + Assert.IsNotNull(probingPaths); + Assert.IsTrue(probingPaths?.Count > 0); + } + + // This test validates that the resolver returns null when the assembly is not found in the probing paths. + [Test] + public void ResolveMissingAssembly_NotFoundInProbingPaths() + { + var trustedPlatformAssemblies = ImmutableArray.Create("path/to/assembly1.dll", "path/to/assembly2.dll"); + var resolver = new Mock() { CallBase = true }; + var referenceIdentity = new AssemblyIdentity("Assembly1"); + resolver.Setup(r => r.FileExists(It.IsAny())).Returns(false); + var result = resolver.Object.ResolveMissingAssembly(null!, referenceIdentity); + + Assert.IsNull(result); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/Configuration.json b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/Configuration.json new file mode 100644 index 0000000000..f34eb20cbc --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/Configuration.json @@ -0,0 +1,8 @@ +{ + "output-folder": "./outputFolder", + "project-folder": "./projectFolder", + "namespace": "sample.namespace", + "unknown-bool-property": false, + "library-name": "sample-library", + "unknown-string-property": "unknownPropertyValue" +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/Expressions/MockExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/Expressions/MockExpression.cs new file mode 100644 index 0000000000..396c7dde9f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/Expressions/MockExpression.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Generator.CSharp.Expressions; + +namespace Microsoft.Generator.CSharp.Tests +{ + internal record MockExpression : ValueExpression + { + public override void Write(CodeWriter writer) + { + writer.AppendRaw("Custom implementation"); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/MockCodeModelPlugin.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/MockCodeModelPlugin.cs new file mode 100644 index 0000000000..38c7346b21 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/MockCodeModelPlugin.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Generator.CSharp.Expressions; +using Microsoft.Generator.CSharp.Input; + +namespace Microsoft.Generator.CSharp.Tests +{ + internal class MockCodeModelPlugin : CodeModelPlugin + { + private static MockCodeModelPlugin? _instance; +#pragma warning disable CS0108 // Member hides inherited member; missing new keyword + internal static MockCodeModelPlugin Instance => _instance ?? throw new InvalidOperationException("ClientModelPlugin is not loaded."); +#pragma warning restore CS0108 // Member hides inherited member; missing new keyword + public MockCodeModelPlugin(GeneratorContext context) : base(context) + { + _instance = this; + } + + public override ApiTypes ApiTypes => throw new NotImplementedException(); + public override CodeWriterExtensionMethods CodeWriterExtensionMethods => new CustomCodeWriterExtensionMethods(); + public override TypeFactory TypeFactory => throw new NotImplementedException(); + public override ExtensibleSnippets ExtensibleSnippets => throw new NotImplementedException(); + public override OutputLibrary GetOutputLibrary(InputNamespace input) => throw new NotImplementedException(); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/Writers/CustomCodeWriterExtensionMethods.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/Writers/CustomCodeWriterExtensionMethods.cs new file mode 100644 index 0000000000..b3bd321a60 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/mocks/Writers/CustomCodeWriterExtensionMethods.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Generator.CSharp.Tests +{ + internal class CustomCodeWriterExtensionMethods : CodeWriterExtensionMethods + { + public override string LicenseString => "// License string"; + + public override void WriteMethod(CodeWriter writer, Method method) + { + writer.AppendRaw("Custom implementation"); + } + } +} diff --git a/packages/http-client-csharp/generator/Packages.Data.props b/packages/http-client-csharp/generator/Packages.Data.props new file mode 100644 index 0000000000..b0ad6b8218 --- /dev/null +++ b/packages/http-client-csharp/generator/Packages.Data.props @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/http-client-csharp/generator/README.md b/packages/http-client-csharp/generator/README.md new file mode 100644 index 0000000000..3e6ee6d669 --- /dev/null +++ b/packages/http-client-csharp/generator/README.md @@ -0,0 +1,20 @@ +# Microsoft Generator CSharp + +The **Microsoft Generator CSharp** tool generates client libraries for accessing RESTful web services. + +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Build](#build) + +## Prerequisites + +- [.NET Core SDK (8.0.x)](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) + +## Build + +1. `dotnet build` (at root) + +## Test + +1. `dotnet test` (at root) diff --git a/packages/http-client-csharp/generator/docs/generate/options.md b/packages/http-client-csharp/generator/docs/generate/options.md new file mode 100644 index 0000000000..a54a957948 --- /dev/null +++ b/packages/http-client-csharp/generator/docs/generate/options.md @@ -0,0 +1,22 @@ +# Microsoft Generator CSharp Usage & Options + +The command line usage of the generator is comprised of the following: + +> `mgc.exe [DIRECTORY] [additional options]` + +## Inputs + +The following generator inputs are supported: + +| Input | Description | Required | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `DIRECTORY` | The path to the directory containing the input files to the generator including the code model file and the configuration file for the generator. | Yes | + +## Supported Flags + +| Flag | Description | Required | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `--model-plugin=` | The name of the custom client model plugin NuGet package to use to generate the code. If not provided, the default client model plugin package `Microsoft.Generator.CSharp.ClientModel` will be used. | No | +| `--model-plugin-version=` | The version of the client model plugin package to use. If no custom client model plugin is provided via the `--model-plugin` flag, then this flag will apply to the default client model plugin package. | Yes | +| `-o, --output-path=` | Path to the output directory. If not provided, the output path will default to the specified input `DIRECTORY`. | No | +| `--help` | Display help. | No | diff --git a/packages/http-client-csharp/generator/global.json b/packages/http-client-csharp/generator/global.json new file mode 100644 index 0000000000..3ce77a4557 --- /dev/null +++ b/packages/http-client-csharp/generator/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.204", + "rollForward": "feature" + } +} diff --git a/packages/http-client-csharp/package-lock.json b/packages/http-client-csharp/package-lock.json index 3e1b46ea54..8ca6a34cf0 100644 --- a/packages/http-client-csharp/package-lock.json +++ b/packages/http-client-csharp/package-lock.json @@ -40,6 +40,12 @@ "@typespec/versioning": ">=0.50.0 <1.0.0" } }, + "../http-client-csharp-generator/artifacts/bin/Microsoft.Generator.CSharp.ClientModel/Debug/net8.0": { + "extraneous": true + }, + "generator/artifacts/bin/Microsoft.Generator.CSharp.ClientModel/Debug/net8.0": { + "extraneous": true + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", diff --git a/packages/http-client-csharp/package.json b/packages/http-client-csharp/package.json index 4ff3b8143f..cf681dca7a 100644 --- a/packages/http-client-csharp/package.json +++ b/packages/http-client-csharp/package.json @@ -17,14 +17,14 @@ "typespec" ], "type": "module", - "main": "dist/index.js", + "main": "emitter/dist/index.js", "exports": { - ".": "./dist/index.js" + ".": "./emitter/dist/index.js" }, "scripts": { - "clean": "rimraf ./dist ./temp", - "build": "tsc -p tsconfig.build.json", - "watch": "tsc -p tsconfig.build.json --watch", + "clean": "rimraf ./emitter/dist ./emitter/temp", + "build": "tsc -p ./emitter/tsconfig.build.json", + "watch": "tsc -p ./emitter/tsconfig.build.json --watch", "lint-typespec-library": "tsp compile . --warn-as-error --import @typespec/library-linter --no-emit", "test": "vitest run", "test:watch": "vitest -w", @@ -35,7 +35,8 @@ "format": "pnpm -w format:dir packages/http-client-csharp" }, "files": [ - "dist/**" + "emitter/dist/**", + "generator/artifacts/bin/release/8.0/**" ], "dependencies": { "json-serialize-refs": "0.1.0-0",