Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 7 additions & 3 deletions packages/http-client-csharp/emitter/src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,19 @@ export async function $onEmit(context: EmitContext<CSharpEmitterOptions>) {
const isValid = await _validateDotNetSdk(sdkContext, _minSupportedDotNetSdkVersion);
// if the dotnet sdk is valid, the error is not dependency issue, log it as normal
if (isValid) {
throw new Error(
`Failed to generate the library. Exit code: ${result.exitCode}.\nStackTrace: \n${result.stderr}`,
sdkContext.logger.error(
`Failed to generate the library. Exit code: ${result.exitCode}.\n${result.stderr}`,
);
}
}
} catch (error: any) {
const isValid = await _validateDotNetSdk(sdkContext, _minSupportedDotNetSdkVersion);
// if the dotnet sdk is valid, the error is not dependency issue, log it as normal
if (isValid) throw new Error(error);
if (isValid) {
sdkContext.logger.error(
`Failed to generate the library. Error: ${error.message ?? error}`,
);
}
}
if (!options["save-inputs"]) {
// delete
Expand Down
20 changes: 20 additions & 0 deletions packages/http-client-csharp/emitter/test/Unit/emitter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,26 @@ describe("$onEmit tests", () => {
expect(updateCallback).toHaveBeenCalledTimes(1);
});

it("should report diagnostic instead of throwing when generator fails", async () => {
vi.mocked(execCSharpGenerator).mockResolvedValueOnce({
exitCode: 1,
stdout: "",
stderr: "Unable to parse required option package-name from configuration.",
} as any);
vi.mocked(execAsync).mockResolvedValueOnce({
exitCode: 0,
stdio: "",
stdout: "9.0.102",
stderr: "",
proc: { pid: 0, output: "", stdout: "", stderr: "", stdin: "" },
} as any);

const context: EmitContext<CSharpEmitterOptions> = createEmitterContext(program);
// should not throw
await $onEmit(context);
expect(program.reportDiagnostics).toHaveBeenCalled();
});

it("should apply sdk-context-options", async () => {
const context: EmitContext<CSharpEmitterOptions> = createEmitterContext(program);
const additionalDecorators = ["Decorator1", "Decorator2"];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ internal set

protected internal virtual void Configure()
{
if (Configuration.PackageName == Configuration.DefaultPackageName)
{
Configuration.PackageName = TypeFactory.PrimaryNamespace;
Emitter.Info($"'package-name' was not specified. Defaulting to namespace '{Configuration.PackageName}'.");
}

foreach (var type in CustomCodeAttributeProviders)
{
AddTypeToKeep(type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public enum UnreferencedTypesHandlingOption

private const string GeneratedFolderName = "Generated";
private const string ConfigurationFileName = "Configuration.json";
internal const string DefaultPackageName = "GeneratedClient";

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
protected Configuration()
Expand Down Expand Up @@ -84,7 +85,7 @@ private static class Options
private string? _testGeneratedDirectory;
internal string TestGeneratedDirectory => _testGeneratedDirectory ??= Path.Combine(TestProjectDirectory, GeneratedFolderName);

public string PackageName { get; }
public string PackageName { get; internal set; }

/// <summary>
/// True if a sample project should be generated.
Expand Down Expand Up @@ -120,7 +121,7 @@ internal static Configuration Load(string outputPath, string? json = null)
return new Configuration(
Path.GetFullPath(outputPath),
ParseAdditionalConfigOptions(root),
ReadRequiredStringOption(root, Options.PackageName),
ReadStringOption(root, Options.PackageName) ?? DefaultPackageName,
ReadOption(root, Options.DisableXmlDocs),
ReadEnumOption<UnreferencedTypesHandlingOption>(root, Options.UnreferencedTypesHandling),
ReadLicenseInfo(root));
Expand Down Expand Up @@ -178,11 +179,6 @@ private static bool ReadOption(JsonElement root, string 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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,48 @@ public void TestParseConfig_OutputFolder(string mockJson, bool throwsError)

// Validates that the LibraryName field is parsed correctly from the configuration
[TestCaseSource("ParseConfigLibraryNameTestCases")]
public void TestParseConfig_LibraryName(string mockJson, bool throwsError)
public void TestParseConfig_LibraryName(string mockJson, string expected)
{
if (throwsError)
{
Assert.Throws<InvalidOperationException>(() => MockHelpers.LoadMockGenerator(configuration: mockJson));
return;
}
MockHelpers.LoadMockGenerator(configuration: mockJson);

var library = CodeModelGenerator.Instance.Configuration.PackageName;
var expected = "libraryName";

Assert.AreEqual(expected, library);
}

// Validates that when package-name is not specified, Configure() defaults to the PrimaryNamespace
[Test]
public void TestParseConfig_PackageNameDefaultsToPrimaryNamespace()
{
var mockJson = @"{
""output-folder"": ""outputFolder""
}";

MockHelpers.LoadMockGenerator(configuration: mockJson, inputNamespaceName: "My.Custom.Namespace");

// Before Configure, package name should be the default
Assert.AreEqual(Configuration.DefaultPackageName, CodeModelGenerator.Instance.Configuration.PackageName);

// After Configure, package name should be resolved from the namespace
CodeModelGenerator.Instance.Configure();
Assert.AreEqual("My.Custom.Namespace", CodeModelGenerator.Instance.Configuration.PackageName);
}

// Validates that when package-name is explicitly set, Configure() does not override it
[Test]
public void TestParseConfig_ExplicitPackageNameNotOverridden()
{
var mockJson = @"{
""output-folder"": ""outputFolder"",
""package-name"": ""ExplicitName""
}";

MockHelpers.LoadMockGenerator(configuration: mockJson, inputNamespaceName: "My.Custom.Namespace");

CodeModelGenerator.Instance.Configure();
Assert.AreEqual("ExplicitName", CodeModelGenerator.Instance.Configuration.PackageName);
}

// Validates that additional configuration options are parsed correctly
[Test]
public void TestParseConfig_AdditionalConfigOptions()
Expand Down Expand Up @@ -273,11 +301,11 @@ public static IEnumerable<TestCaseData> ParseConfigLibraryNameTestCases
{
yield return new TestCaseData(@"{
""output-folder"": ""outputFolder"",
""package-name"": ""libraryName"",
}", false);
""package-name"": ""libraryName""
}", "libraryName");
yield return new TestCaseData(@"{
""output-folder"": ""outputFolder""
}", true);
}", Configuration.DefaultPackageName);
}
}

Expand Down
Loading