Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions packages/http-client-csharp/docs/decorators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: "Decorators"
description: "Decorators exported by @typespec/http-client-csharp"
toc_min_heading_level: 2
toc_max_heading_level: 3
---

## TypeSpec.HttpClient.CSharp

### `@dynamicModel` {#@TypeSpec.HttpClient.CSharp.dynamicModel}

Marks a model or namespace as dynamic, indicating it should generate dynamic model code.
Can be applied to Model or Namespace types.

```typespec
@TypeSpec.HttpClient.CSharp.dynamicModel
```

#### Target

`Model | Namespace`

#### Parameters

None

#### Examples

```tsp
@dynamicModel
model Pet {
name: string;
kind: string;
}

@dynamicModel
namespace PetStore {
model Dog extends Pet {
breed: string;
}
}
```
127 changes: 127 additions & 0 deletions packages/http-client-csharp/docs/emitter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
title: "Emitter usage"
---

## Emitter usage

1. Via the command line

```bash
tsp compile . --emit=@typespec/http-client-csharp
```

2. Via the config

```yaml
emit:
- "@typespec/http-client-csharp"
```

The config can be extended with options as follows:

```yaml
emit:
- "@typespec/http-client-csharp"
options:
"@typespec/http-client-csharp":
option: value
```

## Emitter options

### `emitter-output-dir`

**Type:** `absolutePath`

Defines the emitter output directory. Defaults to `{output-dir}/@typespec/http-client-csharp`
See [Configuring output directory for more info](https://typespec.io/docs/handbook/configuration/configuration/#configuring-output-directory)

### `api-version`

**Type:** `string`

For TypeSpec files using the [`@versioned`](https://typespec.io/docs/libraries/versioning/reference/decorators/#@TypeSpec.Versioning.versioned) decorator, set this option to the version that should be used to generate against.

### `generate-protocol-methods`

**Type:** `boolean`

Set to `false` to skip generation of protocol methods. The default value is `true`.

### `generate-convenience-methods`

**Type:** `boolean`

Set to `false` to skip generation of convenience methods. The default value is `true`.

### `unreferenced-types-handling`

**Type:** `"removeOrInternalize" | "internalize" | "keepAll"`

Defines the strategy on how to handle unreferenced types. The default value is `removeOrInternalize`.

### `new-project`

**Type:** `boolean`

Set to `true` to overwrite the csproj if it already exists. The default value is `false`.

### `save-inputs`

**Type:** `boolean`

Set to `true` to save the `tspCodeModel.json` and `Configuration.json` files that are emitted and used as inputs to the generator. The default value is `false`.

### `package-name`

**Type:** `string`

Define the package name. If not specified, the first namespace defined in the TypeSpec is used as the package name.

### `debug`

**Type:** `boolean`

Set to `true` to automatically attempt to attach to a debugger when executing the C# generator. The default value is `false`.

### `logLevel`

**Type:** `"info" | "debug" | "verbose"`

Set the log level for which to collect traces. The default value is `info`.

### `disable-xml-docs`

**Type:** `boolean`

Set to `true` to disable XML documentation generation. The default value is `false`.

### `generator-name`

**Type:** `string`

The name of the generator. By default this is set to `ScmCodeModelGenerator`. Generator authors can set this to the name of a generator that inherits from `ScmCodeModelGenerator`.

### `emitter-extension-path`

**Type:** `string`

Allows emitter authors to specify the path to a custom emitter package, allowing you to extend the emitter behavior. This should be set to `import.meta.url` if you are using a custom emitter.

### `plugins`

**Type:** `array`

Paths to generator plugin assemblies (DLLs) or directories containing plugin assemblies. Each plugin must contain a class that extends GeneratorPlugin.

### `license`

**Type:** `object`

License information for the generated client code.

### `sdk-context-options`

**Type:** `object`

The SDK context options that implement the `CreateSdkContextOptions` interface from the [`@azure-tools/typespec-client-generator-core`](https://www.npmjs.com/package/@azure-tools/typespec-client-generator-core) package to be used by the CSharp emitter.
41 changes: 41 additions & 0 deletions packages/http-client-csharp/docs/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
title: Overview
sidebar_position: 0
toc_min_heading_level: 2
toc_max_heading_level: 3
---

import { Tabs, TabItem } from "@astrojs/starlight/components";

TypeSpec library for emitting Http Client libraries for C#.

## Install

<Tabs>
<TabItem label="In a spec" default>

```bash
npm install @typespec/http-client-csharp
```

</TabItem>
<TabItem label="In a library" default>

```bash
npm install --save-peer @typespec/http-client-csharp
```

</TabItem>
</Tabs>

## Emitter usage

[See documentation](./emitter.md)

## TypeSpec.HttpClient

## TypeSpec.HttpClient.CSharp

### Decorators

- [`@dynamicModel`](./decorators.md#@TypeSpec.HttpClient.CSharp.dynamicModel)
7 changes: 6 additions & 1 deletion packages/http-client-csharp/emitter/src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
resolvePath,
} from "@typespec/compiler";
import fs, { statSync } from "fs";
import { dirname } from "path";
import { dirname, resolve } from "path";
import { fileURLToPath } from "url";
import { writeCodeModel, writeConfiguration } from "./code-model-writer.js";
import {
Expand Down Expand Up @@ -84,6 +84,11 @@ export async function emitCodeModel(
const options = resolveOptions(context);
const outputFolder = context.emitterOutputDir;

// Resolve plugin paths to absolute if specified
if (options["plugins"]) {
options["plugins"] = options["plugins"].map((p) => resolve(outputFolder, p));
}

/* set the log level. */
const logger = new Logger(program, options.logLevel ?? LoggerLevel.INFO);

Expand Down
9 changes: 9 additions & 0 deletions packages/http-client-csharp/emitter/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface CSharpEmitterOptions {
"disable-xml-docs"?: boolean;
"generator-name"?: string;
"emitter-extension-path"?: string;
plugins?: string[];
"sdk-context-options"?: CreateSdkContextOptions;
"generate-protocol-methods"?: boolean;
"generate-convenience-methods"?: boolean;
Expand Down Expand Up @@ -113,6 +114,14 @@ export const CSharpEmitterOptionsSchema: JSONSchemaType<CSharpEmitterOptions> =
description:
"Allows emitter authors to specify the path to a custom emitter package, allowing you to extend the emitter behavior. This should be set to `import.meta.url` if you are using a custom emitter.",
},
plugins: {
type: "array",
items: { type: "string" },
nullable: true,
description:
"Paths to generator plugin assemblies (DLLs) or directories containing plugin assemblies. " +
"Each plugin must contain a class that extends GeneratorPlugin.",
},
license: {
type: "object",
additionalProperties: false,
Expand Down
23 changes: 23 additions & 0 deletions packages/http-client-csharp/emitter/test/Unit/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,27 @@ describe("Configuration tests", async () => {
expect(config["generate-protocol-methods"]).toBeUndefined();
expect(config["generate-convenience-methods"]).toBeUndefined();
});

it("should pass plugins option to configuration", async () => {
const options: CSharpEmitterOptions = {
"package-name": "test-package",
plugins: ["/path/to/Plugin.dll", "/path/to/plugin-dir"],
};
const context = createEmitterContext(program, options);
const sdkContext = await createCSharpSdkContext(context);
const config = createConfiguration(options, "namespace", sdkContext);

expect(config["plugins"]).toEqual(["/path/to/Plugin.dll", "/path/to/plugin-dir"]);
});

it("should not include plugins in configuration when not set", async () => {
const options: CSharpEmitterOptions = {
"package-name": "test-package",
};
const context = createEmitterContext(program, options);
const sdkContext = await createCSharpSdkContext(context);
const config = createConfiguration(options, "namespace", sdkContext);

expect(config["plugins"]).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ public Configuration(
string packageName,
bool disableXmlDocs,
UnreferencedTypesHandlingOption unreferencedTypesHandling,
LicenseInfo? licenseInfo)
LicenseInfo? licenseInfo,
IReadOnlyList<string>? pluginPaths = null)
{
OutputDirectory = outputPath;
AdditionalConfigurationOptions = additionalConfigurationOptions;
PackageName = packageName;
DisableXmlDocs = disableXmlDocs;
UnreferencedTypesHandling = unreferencedTypesHandling;
LicenseInfo = licenseInfo;
PluginPaths = pluginPaths;
}

/// <summary>
Expand All @@ -53,6 +55,7 @@ private static class Options
public const string PackageName = "package-name";
public const string DisableXmlDocs = "disable-xml-docs";
public const string UnreferencedTypesHandling = "unreferenced-types-handling";
public const string Plugins = "plugins";
}

/// <summary>
Expand Down Expand Up @@ -86,6 +89,13 @@ private static class Options

public string PackageName { get; }

/// <summary>
/// Gets the paths to plugin assemblies (DLLs) or directories containing plugin assemblies.
/// When specified, the generator loads plugins from these paths in addition to any
/// plugins discovered via node_modules.
/// </summary>
public IReadOnlyList<string>? PluginPaths { get; }

/// <summary>
/// True if a sample project should be generated.
/// </summary>
Expand Down Expand Up @@ -123,7 +133,8 @@ internal static Configuration Load(string outputPath, string? json = null)
ReadRequiredStringOption(root, Options.PackageName),
ReadOption(root, Options.DisableXmlDocs),
ReadEnumOption<UnreferencedTypesHandlingOption>(root, Options.UnreferencedTypesHandling),
ReadLicenseInfo(root));
ReadLicenseInfo(root),
ReadStringArrayOption(root, Options.Plugins));
}

private static LicenseInfo? ReadLicenseInfo(JsonElement root)
Expand Down Expand Up @@ -164,6 +175,7 @@ internal static Configuration Load(string outputPath, string? json = null)
Options.PackageName,
Options.DisableXmlDocs,
Options.UnreferencedTypesHandling,
Options.Plugins,
};

private static bool ReadOption(JsonElement root, string option)
Expand Down Expand Up @@ -191,6 +203,25 @@ private static string ReadRequiredStringOption(JsonElement root, string option)
return null;
}

private static IReadOnlyList<string>? ReadStringArrayOption(JsonElement root, string option)
{
if (root.TryGetProperty(option, out JsonElement value) && value.ValueKind == JsonValueKind.Array)
{
var list = new List<string>();
foreach (var item in value.EnumerateArray())
{
var str = item.GetString();
if (!string.IsNullOrEmpty(str))
{
list.Add(str);
}
}
return list.Count > 0 ? list : null;
}

return null;
}

/// <summary>
/// Returns the default value for the given option.
/// </summary>
Expand Down
Loading
Loading