diff --git a/.chronus/changes/fix-discriminator-should-be-requiered-2024-9-9-17-24-19.md b/.chronus/changes/fix-discriminator-should-be-requiered-2024-9-9-17-24-19.md new file mode 100644 index 0000000000..d854fd90d3 --- /dev/null +++ b/.chronus/changes/fix-discriminator-should-be-requiered-2024-9-9-17-24-19.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/openapi3" +--- + +Discriminator properties are marked as required regardless if they are in TypeSpec to match OpenAPI3 spec. diff --git a/.chronus/changes/fix-editor-dark-theme-2024-9-14-8-3-1.md b/.chronus/changes/fix-editor-dark-theme-2024-9-14-8-3-1.md new file mode 100644 index 0000000000..20b986f0c4 --- /dev/null +++ b/.chronus/changes/fix-editor-dark-theme-2024-9-14-8-3-1.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/playground" +--- + +Fix dark theme not applying diff --git a/.chronus/changes/tsp-openapi3-common-params-2024-9-11-14-26-2.md b/.chronus/changes/tsp-openapi3-common-params-2024-9-11-14-26-2.md new file mode 100644 index 0000000000..1bd62f1866 --- /dev/null +++ b/.chronus/changes/tsp-openapi3-common-params-2024-9-11-14-26-2.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/openapi3" +--- + +Updates tsp-openapi3 to include path-level parameters in generated typespec operations. \ No newline at end of file diff --git a/.chronus/config.yaml b/.chronus/config.yaml index 1aa7cecd08..0db0cc476a 100644 --- a/.chronus/config.yaml +++ b/.chronus/config.yaml @@ -51,6 +51,10 @@ versionPolicies: - "typespec-vs" - "typespec-vscode" - "@typespec/library-linter" + - "@typespec/events" + - "@typespec/sse" + - "@typespec/streams" + - "@typespec/xml" changelog: ["@chronus/github/changelog", { repo: "microsoft/typespec" }] diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs index 4d33051b0f..53dd9ba593 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs @@ -1311,7 +1311,7 @@ private static MethodBodyStatement ThrowValidationFailException(ValueExpression /// private MethodBodyStatement[] CreateWritePropertiesStatements() { - var properties = _model.Properties.Concat(_model.CustomCodeView?.Properties.Where(p => p.WireInfo != null) ?? []); + var properties = _model.CanonicalView.Properties; List propertyStatements = new(); foreach (var property in properties) { diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs index 46e74868ac..fe58495d83 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs @@ -152,8 +152,9 @@ public async Task CanReplaceOpMethod() } // Validates that a method with a struct parameter can be replaced - [Test] - public async Task CanReplaceStructMethod() + [TestCase(true)] + [TestCase(false)] + public async Task CanReplaceStructMethod(bool isStructCustomized) { var inputOperation = InputFactory.Operation("HelloAgain", parameters: [ @@ -162,7 +163,7 @@ public async Task CanReplaceStructMethod() var inputClient = InputFactory.Client("TestClient", operations: [inputOperation]); var plugin = await MockHelpers.LoadMockPluginAsync( clients: () => [inputClient], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(isStructCustomized.ToString())); // Find the client provider var clientProvider = plugin.Object.OutputLibrary.TypeProviders.SingleOrDefault(t => t is ClientProvider); @@ -189,6 +190,8 @@ public async Task CanReplaceStructMethod() Assert.AreEqual(1, customMethodParams.Count); Assert.AreEqual("p1", customMethodParams[0].Name); Assert.AreEqual("MyStruct", customMethodParams[0].Type.Name); + Assert.AreEqual(isStructCustomized ? "Sample.TestClient" : string.Empty, customMethodParams[0].Type.Namespace); + Assert.IsTrue(customMethodParams[0].Type.IsStruct); Assert.IsTrue(customMethodParams[0].Type.IsNullable); } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod/CanReplaceStructMethod.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod(False)/CanReplaceStructMethod.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod/CanReplaceStructMethod.cs rename to packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod(False)/CanReplaceStructMethod.cs diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod(True)/CanReplaceStructMethod.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod(True)/CanReplaceStructMethod.cs new file mode 100644 index 0000000000..cc6387df2e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod(True)/CanReplaceStructMethod.cs @@ -0,0 +1,23 @@ + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sample +{ + /// + public partial class TestClient + { + public virtual ClientResult HelloAgain(MyStruct? p1) + { + + } + + public readonly partial struct MyStruct + { + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs index bcc736508d..b48d273ef3 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs @@ -35,7 +35,6 @@ public async Task CanChangePropertyName() // validate the methods use the custom member name var writer = new TypeProviderWriter(modelProvider); var file = writer.Write(); - var expected = Helpers.GetExpectedFromFile(); Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs index 62566bf7d1..6dfbe6c893 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs @@ -1,6 +1,7 @@ // 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.Generator.CSharp.ClientModel.Providers; @@ -212,6 +213,35 @@ public async Task CanChangeDictionaryToBinaryData() Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } + private static IEnumerable ExtensibleEnumCases => + [ + new TestCaseData(InputPrimitiveType.String), + new TestCaseData(InputPrimitiveType.Int32), + ]; + + [TestCaseSource(nameof(ExtensibleEnumCases))] + public async Task CanCustomizeExtensibleEnum(InputPrimitiveType enumType) + { + var props = new[] + { + InputFactory.Property("Prop1", InputFactory.Enum("EnumType", enumType, isExtensible: true)) + }; + + var inputModel = InputFactory.Model("mockInputModel", properties: props, usage: InputModelTypeUsage.Json); + var plugin = await MockHelpers.LoadMockPluginAsync( + inputModels: () => [inputModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(enumType.Name)); + + var modelProvider = plugin.Object.OutputLibrary.TypeProviders.Single(t => t is ModelProvider); + var serializationProvider = modelProvider.SerializationProviders.Single(t => t is MrwSerializationTypeDefinition); + Assert.IsNotNull(serializationProvider); + Assert.AreEqual(0, serializationProvider.Fields.Count); + + var writer = new TypeProviderWriter(serializationProvider); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(enumType.Name), file.Content); + } + [Test] public async Task CanReplaceSerializationMethod() { diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32).cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32).cs new file mode 100644 index 0000000000..4032f62c2b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32).cs @@ -0,0 +1,145 @@ +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; +using Sample; + +namespace Sample.Models +{ + /// + public partial class MockInputModel : global::System.ClientModel.Primitives.IJsonModel + { + void global::System.ClientModel.Primitives.IJsonModel.Write(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + writer.WriteStartObject(); + this.JsonModelWriteCore(writer, options); + writer.WriteEndObject(); + } + + /// The JSON writer. + /// The client options for reading and writing models. + protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support writing '{format}' format."); + } + writer.WritePropertyName("prop1"u8); + writer.WriteNumberValue(Prop1.ToSerialInt32()); + if (((options.Format != "W") && (_additionalBinaryDataProperties != null))) + { + foreach (var item in _additionalBinaryDataProperties) + { + writer.WritePropertyName(item.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(item.Value); +#else + using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(item.Value)) + { + global::System.Text.Json.JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + } + } + + global::Sample.Models.MockInputModel global::System.ClientModel.Primitives.IJsonModel.Create(ref global::System.Text.Json.Utf8JsonReader reader, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => ((global::Sample.Models.MockInputModel)this.JsonModelCreateCore(ref reader, options)); + + /// The JSON reader. + /// The client options for reading and writing models. + protected virtual global::Sample.Models.MockInputModel JsonModelCreateCore(ref global::System.Text.Json.Utf8JsonReader reader, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support reading '{format}' format."); + } + using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.ParseValue(ref reader); + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, options); + } + + internal static global::Sample.Models.MockInputModel DeserializeMockInputModel(global::System.Text.Json.JsonElement element, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + if ((element.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + return null; + } + global::Sample.Models.EnumType prop1 = default; + global::System.Collections.Generic.IDictionary additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("prop1"u8)) + { + if ((prop.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + prop1 = null; + continue; + } + prop1 = new global::Sample.Models.EnumType(prop.Value.GetInt32()); + continue; + } + if ((options.Format != "W")) + { + additionalBinaryDataProperties.Add(prop.Name, global::System.BinaryData.FromString(prop.Value.GetRawText())); + } + } + return new global::Sample.Models.MockInputModel(prop1, additionalBinaryDataProperties); + } + + global::System.BinaryData global::System.ClientModel.Primitives.IPersistableModel.Write(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => this.PersistableModelWriteCore(options); + + /// The client options for reading and writing models. + protected virtual global::System.BinaryData PersistableModelWriteCore(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + return global::System.ClientModel.Primitives.ModelReaderWriter.Write(this, options); + default: + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support writing '{options.Format}' format."); + } + } + + global::Sample.Models.MockInputModel global::System.ClientModel.Primitives.IPersistableModel.Create(global::System.BinaryData data, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => ((global::Sample.Models.MockInputModel)this.PersistableModelCreateCore(data, options)); + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual global::Sample.Models.MockInputModel PersistableModelCreateCore(global::System.BinaryData data, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(data)) + { + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, options); + } + default: + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support reading '{options.Format}' format."); + } + } + + string global::System.ClientModel.Primitives.IPersistableModel.GetFormatFromOptions(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => "J"; + + /// The to serialize into . + public static implicit operator BinaryContent(global::Sample.Models.MockInputModel mockInputModel) + { + return global::System.ClientModel.BinaryContent.Create(mockInputModel, global::Sample.ModelSerializationExtensions.WireOptions); + } + + /// The to deserialize the from. + public static explicit operator MockInputModel(global::System.ClientModel.ClientResult result) + { + using global::System.ClientModel.Primitives.PipelineResponse response = result.GetRawResponse(); + using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(response.Content); + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, global::Sample.ModelSerializationExtensions.WireOptions); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32)/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32)/MockInputModel.cs new file mode 100644 index 0000000000..a27e763526 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32)/MockInputModel.cs @@ -0,0 +1,17 @@ +#nullable disable + +using Microsoft.Generator.CSharp.Customization; +using Microsoft.Generator.CSharp.Primitives; + +namespace Sample.Models +{ + public partial class MockInputModel + { + public EnumType Prop1 { get; set; } + } + + public readonly partial struct EnumType + { + public static EnumType Foo = new EnumType(1); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string).cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string).cs new file mode 100644 index 0000000000..68d078fc24 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string).cs @@ -0,0 +1,145 @@ +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; +using Sample; + +namespace Sample.Models +{ + /// + public partial class MockInputModel : global::System.ClientModel.Primitives.IJsonModel + { + void global::System.ClientModel.Primitives.IJsonModel.Write(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + writer.WriteStartObject(); + this.JsonModelWriteCore(writer, options); + writer.WriteEndObject(); + } + + /// The JSON writer. + /// The client options for reading and writing models. + protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support writing '{format}' format."); + } + writer.WritePropertyName("prop1"u8); + writer.WriteStringValue(Prop1.ToString()); + if (((options.Format != "W") && (_additionalBinaryDataProperties != null))) + { + foreach (var item in _additionalBinaryDataProperties) + { + writer.WritePropertyName(item.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(item.Value); +#else + using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(item.Value)) + { + global::System.Text.Json.JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + } + } + + global::Sample.Models.MockInputModel global::System.ClientModel.Primitives.IJsonModel.Create(ref global::System.Text.Json.Utf8JsonReader reader, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => ((global::Sample.Models.MockInputModel)this.JsonModelCreateCore(ref reader, options)); + + /// The JSON reader. + /// The client options for reading and writing models. + protected virtual global::Sample.Models.MockInputModel JsonModelCreateCore(ref global::System.Text.Json.Utf8JsonReader reader, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support reading '{format}' format."); + } + using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.ParseValue(ref reader); + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, options); + } + + internal static global::Sample.Models.MockInputModel DeserializeMockInputModel(global::System.Text.Json.JsonElement element, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + if ((element.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + return null; + } + global::Sample.Models.EnumType prop1 = default; + global::System.Collections.Generic.IDictionary additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("prop1"u8)) + { + if ((prop.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + prop1 = null; + continue; + } + prop1 = new global::Sample.Models.EnumType(prop.Value.GetString()); + continue; + } + if ((options.Format != "W")) + { + additionalBinaryDataProperties.Add(prop.Name, global::System.BinaryData.FromString(prop.Value.GetRawText())); + } + } + return new global::Sample.Models.MockInputModel(prop1, additionalBinaryDataProperties); + } + + global::System.BinaryData global::System.ClientModel.Primitives.IPersistableModel.Write(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => this.PersistableModelWriteCore(options); + + /// The client options for reading and writing models. + protected virtual global::System.BinaryData PersistableModelWriteCore(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + return global::System.ClientModel.Primitives.ModelReaderWriter.Write(this, options); + default: + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support writing '{options.Format}' format."); + } + } + + global::Sample.Models.MockInputModel global::System.ClientModel.Primitives.IPersistableModel.Create(global::System.BinaryData data, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => ((global::Sample.Models.MockInputModel)this.PersistableModelCreateCore(data, options)); + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual global::Sample.Models.MockInputModel PersistableModelCreateCore(global::System.BinaryData data, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(data)) + { + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, options); + } + default: + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support reading '{options.Format}' format."); + } + } + + string global::System.ClientModel.Primitives.IPersistableModel.GetFormatFromOptions(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => "J"; + + /// The to serialize into . + public static implicit operator BinaryContent(global::Sample.Models.MockInputModel mockInputModel) + { + return global::System.ClientModel.BinaryContent.Create(mockInputModel, global::Sample.ModelSerializationExtensions.WireOptions); + } + + /// The to deserialize the from. + public static explicit operator MockInputModel(global::System.ClientModel.ClientResult result) + { + using global::System.ClientModel.Primitives.PipelineResponse response = result.GetRawResponse(); + using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(response.Content); + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, global::Sample.ModelSerializationExtensions.WireOptions); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string)/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string)/MockInputModel.cs new file mode 100644 index 0000000000..93fccae2ce --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string)/MockInputModel.cs @@ -0,0 +1,17 @@ +#nullable disable + +using Microsoft.Generator.CSharp.Customization; +using Microsoft.Generator.CSharp.Primitives; + +namespace Sample.Models +{ + public partial class MockInputModel + { + public EnumType Prop1 { get; set; } + } + + public readonly partial struct EnumType + { + public static EnumType Foo = new EnumType("Foo"); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/MemberExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/MemberExpression.cs index fb36ee9e55..b02f4e7c4c 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/MemberExpression.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/MemberExpression.cs @@ -12,7 +12,9 @@ internal override void Write(CodeWriter writer) Inner.Write(writer); writer.AppendRaw("."); } - writer.AppendRaw(MemberName); + // workaround to avoid Roslyn reducing properties named Object to object + // Should come up with a better approach - https://github.com/microsoft/typespec/issues/4724 + writer.AppendRaw(MemberName == "Object" && Inner == null ? $"this.{MemberName}" : MemberName); } } } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/CanonicalTypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/CanonicalTypeProvider.cs new file mode 100644 index 0000000000..dfb8c31108 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/CanonicalTypeProvider.cs @@ -0,0 +1,160 @@ +// 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.Input; +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.SourceInput; + +namespace Microsoft.Generator.CSharp.Providers +{ + internal class CanonicalTypeProvider : TypeProvider + { + private readonly InputModelType? _inputModel; + private readonly TypeProvider _generatedTypeProvider; + + public CanonicalTypeProvider(TypeProvider generatedTypeProvider, InputType? inputType) + { + _generatedTypeProvider = generatedTypeProvider; + _inputModel = inputType as InputModelType; + } + protected override string BuildRelativeFilePath() => throw new InvalidOperationException("This type should not be writing in generation"); + + protected override string BuildName() => _generatedTypeProvider.Name; + + protected override string GetNamespace() => _generatedTypeProvider.Namespace; + + protected override TypeSignatureModifiers GetDeclarationModifiers() => _generatedTypeProvider.DeclarationModifiers; + + private protected override PropertyProvider[] FilterCustomizedProperties(PropertyProvider[] canonicalProperties) => canonicalProperties; + + private protected override CanonicalTypeProvider GetCanonicalView() => this; + + // TODO - Implement BuildMethods, BuildConstructors, etc as needed + + protected override PropertyProvider[] BuildProperties() + { + var specProperties = _inputModel?.Properties ?? []; + var specPropertiesMap = specProperties.ToDictionary(p => p.Name.ToCleanName(), p => p); + var generatedProperties = _generatedTypeProvider.Properties; + var customProperties = _generatedTypeProvider.CustomCodeView?.Properties ?? []; + + Dictionary serializedNameMapping = BuildSerializationNameMap(); + + // Update the serializedName of generated properties if necessary + foreach (var generatedProperty in generatedProperties) + { + if (serializedNameMapping.TryGetValue(generatedProperty.Name, out var serializedName) && serializedName != null) + { + generatedProperty.WireInfo!.SerializedName = serializedName; + } + } + + Dictionary specToCustomPropertiesMap = BuildSpecToCustomPropertyMap(customProperties, specPropertiesMap); + + foreach (var customProperty in customProperties) + { + InputModelProperty? specProperty = null; + + if (((customProperty.OriginalName != null && specPropertiesMap.TryGetValue(customProperty.OriginalName, out var candidateSpecProperty)) + || specPropertiesMap.TryGetValue(customProperty.Name, out candidateSpecProperty)) + // Ensure that the spec property is mapped to this custom property + && specToCustomPropertiesMap[candidateSpecProperty] == customProperty) + { + specProperty = candidateSpecProperty; + customProperty.WireInfo = new PropertyWireInformation(specProperty); + } + + string? serializedName = specProperty?.SerializedName; + bool hasCustomSerialization = false; + // Update the serializedName of custom properties if necessary + if (serializedNameMapping.TryGetValue(customProperty.Name, out var customSerializedName) || + (customProperty.OriginalName != null && serializedNameMapping.TryGetValue(customProperty.OriginalName, out customSerializedName))) + { + hasCustomSerialization = true; + if (customSerializedName != null) + { + serializedName = customSerializedName; + } + } + + if (serializedName != null || hasCustomSerialization) + { + if (specProperty == null) + { + customProperty.WireInfo = new( + SerializationFormat.Default, + false, + !customProperty.Body.HasSetter, + customProperty.Type.IsNullable, + false, + serializedName ?? customProperty.Name.ToVariableName());; + } + else + { + customProperty.WireInfo!.SerializedName = serializedName!; + } + } + + // handle customized extensible enums, since the custom type would not be an enum, but the spec type would be an enum + if (specProperty?.Type is InputEnumType { IsExtensible: true } inputEnumType) + { + customProperty.Type = new CSharpType( + customProperty.Type.Name, + customProperty.Type.Namespace, + customProperty.Type.IsValueType, + customProperty.Type.IsNullable, + customProperty.Type.DeclaringType, + customProperty.Type.Arguments, + customProperty.Type.IsPublic, + customProperty.Type.IsStruct, + customProperty.Type.BaseType, + TypeFactory.CreatePrimitiveCSharpTypeCore(inputEnumType.ValueType)); + } + } + + return [..generatedProperties, ..customProperties]; + } + + private static Dictionary BuildSpecToCustomPropertyMap( + IReadOnlyList customProperties, + Dictionary specPropertiesMap) + { + var specToCustomPropertiesMap = new Dictionary(); + // Create a map from spec properties to custom properties so that we know which custom properties are replacing spec properties + foreach (var customProperty in customProperties) + { + if ((customProperty.OriginalName != null && specPropertiesMap.TryGetValue(customProperty.OriginalName, out var specProperty)) + || specPropertiesMap.TryGetValue(customProperty.Name, out specProperty)) + { + // If the spec property is not already mapped to a custom property, map it to this custom property + specToCustomPropertiesMap.TryAdd(specProperty, customProperty); + } + } + return specToCustomPropertiesMap; + } + + private Dictionary BuildSerializationNameMap() + { + var serializationAttributes = _generatedTypeProvider.CustomCodeView?.GetAttributes(). + Where(a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenSerializationAttributeName) ?? []; + var serializedNameMapping = new Dictionary(); + foreach (var serializationAttribute in serializationAttributes) + { + if (CodeGenAttributes.TryGetCodeGenSerializationAttributeValue( + serializationAttribute, + out var propertyName, + out string? serializationName, + out _, + out _, + out _)) + { + serializedNameMapping[propertyName] = serializationName; + } + } + return serializedNameMapping; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs index 5db762628e..2e68330ec3 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs @@ -30,7 +30,7 @@ public sealed class ModelProvider : TypeProvider private ModelProvider? _baseModelProvider; private ConstructorProvider? _fullConstructor; - public ModelProvider(InputModelType inputModel) + public ModelProvider(InputModelType inputModel) : base(inputModel) { _inputModel = inputModel; Description = inputModel.Description != null ? FormattableStringHelpers.FromString(inputModel.Description) : $"The {Name}."; @@ -85,8 +85,6 @@ public ModelProvider? BaseModelProvider internal bool SupportsBinaryDataAdditionalProperties => AdditionalPropertyProperties.Any(p => p.Type.ElementType.Equals(_additionalPropsUnknownType)); public ConstructorProvider FullConstructor => _fullConstructor ??= BuildFullConstructor(); - internal IReadOnlyList AllSpecProperties => Properties.Concat(CustomCodeView?.Properties.Where(p => p.WireInfo != null) ?? []).ToList(); - protected override string GetNamespace() => CodeModelPlugin.Instance.Configuration.ModelNamespace; protected override CSharpType? GetBaseType() @@ -459,14 +457,14 @@ private ConstructorProvider BuildFullConstructor() if (isPrimaryConstructor) { // the primary ctor should only include the properties of the direct base model - baseProperties = BaseModelProvider?.Properties ?? []; + baseProperties = BaseModelProvider?.CanonicalView.Properties ?? []; } else if (BaseModelProvider?.FullConstructor.Signature != null) { baseParameters.AddRange(BaseModelProvider.FullConstructor.Signature.Parameters); } - HashSet overriddenProperties = AllSpecProperties.Where(p => p.BaseProperty is not null).Select(p => p.BaseProperty!).ToHashSet(); + HashSet overriddenProperties = CanonicalView.Properties.Where(p => p.BaseProperty is not null).Select(p => p.BaseProperty!).ToHashSet(); // add the base parameters, if any foreach (var property in baseProperties) @@ -477,7 +475,7 @@ private ConstructorProvider BuildFullConstructor() // construct the initializer using the parameters from base signature var constructorInitializer = new ConstructorInitializer(true, [.. baseParameters.Select(p => GetExpressionForCtor(p, overriddenProperties, isPrimaryConstructor))]); - foreach (var property in AllSpecProperties) + foreach (var property in CanonicalView.Properties) { AddInitializationParameterForCtor(constructorParameters, property, Type.IsStruct, isPrimaryConstructor); } @@ -508,7 +506,7 @@ p.Property is null { if (_inputModel.BaseModel is not null && _inputModel.DiscriminatorValue is not null) { - var discriminator = BaseModelProvider?.Properties.Where(p => p.IsDiscriminator).FirstOrDefault(); + var discriminator = BaseModelProvider?.CanonicalView.Properties.Where(p => p.IsDiscriminator).FirstOrDefault(); if (discriminator != null) { var type = discriminator.Type; @@ -518,12 +516,12 @@ p.Property is null } else { - if (!type.IsFrameworkType && type.IsEnum) + if (!type.IsFrameworkType && type.IsEnum && _inputModel.BaseModel.DiscriminatorProperty!.Type is InputEnumType inputEnumType) { /* TODO: when customize the discriminator type to a enum, then we may not be able to get the correct TypeProvider in this way. * We will handle this when issue https://github.com/microsoft/typespec/issues/4313 is resolved. * */ - var discriminatorProvider = CodeModelPlugin.Instance.TypeFactory.CreateEnum(enumType: (InputEnumType)_inputModel.BaseModel.DiscriminatorProperty!.Type); + var discriminatorProvider = CodeModelPlugin.Instance.TypeFactory.CreateEnum(enumType: inputEnumType); var enumMember = discriminatorProvider!.EnumValues.FirstOrDefault(e => e.Value.ToString() == _inputModel.DiscriminatorValue) ?? throw new InvalidOperationException($"invalid discriminator value {_inputModel.DiscriminatorValue}"); /* {KindType}.{enumMember} */ return TypeReferenceExpression.FromType(type).Property(enumMember.Name); @@ -619,10 +617,10 @@ private MethodBodyStatement GetPropertyInitializers( bool isPrimaryConstructor, IReadOnlyList? parameters = null) { - List methodBodyStatements = new(Properties.Count + 1); + List methodBodyStatements = new(CanonicalView.Properties.Count + 1); Dictionary parameterMap = parameters?.ToDictionary(p => p.Name) ?? []; - foreach (var property in AllSpecProperties) + foreach (var property in CanonicalView.Properties) { // skip those non-spec properties if (property.WireInfo == null) diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/NamedTypeSymbolProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/NamedTypeSymbolProvider.cs index dc5a15b153..0fb6f041aa 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/NamedTypeSymbolProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/NamedTypeSymbolProvider.cs @@ -8,6 +8,7 @@ using System.Xml.Linq; using Microsoft.CodeAnalysis; using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.SourceInput; using Microsoft.Generator.CSharp.Statements; namespace Microsoft.Generator.CSharp.Providers @@ -108,6 +109,13 @@ protected override PropertyProvider[] BuildProperties() List properties = new List(); foreach (var propertySymbol in _namedTypeSymbol.GetMembers().OfType()) { + var codeGenAttribute = propertySymbol.GetAttributes().SingleOrDefault( + a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenMemberAttributeName); + string? originalName = null; + if (codeGenAttribute != null) + { + CodeGenAttributes.TryGetCodeGenMemberAttributeValue(codeGenAttribute, out originalName); + } var propertyProvider = new PropertyProvider( GetSymbolXmlDoc(propertySymbol, "summary"), GetAccessModifier(propertySymbol.DeclaredAccessibility), @@ -116,7 +124,7 @@ protected override PropertyProvider[] BuildProperties() new AutoPropertyBody(propertySymbol.SetMethod is not null), this) { - Attributes = propertySymbol.GetAttributes() + OriginalName = originalName }; properties.Add(propertyProvider); } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/PropertyProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/PropertyProvider.cs index d56284fbce..8b2ab9480f 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/PropertyProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/PropertyProvider.cs @@ -23,7 +23,7 @@ public class PropertyProvider public FormattableString Description { get; } public XmlDocSummaryStatement XmlDocSummary { get; } public MethodSignatureModifiers Modifiers { get; internal set; } - public CSharpType Type { get; } + public CSharpType Type { get; internal set; } public string Name { get; internal set; } public PropertyBody Body { get; internal set; } public CSharpType? ExplicitInterface { get; } @@ -42,7 +42,7 @@ public class PropertyProvider public TypeProvider EnclosingType { get; } - internal IEnumerable? Attributes { get; init; } + internal string? OriginalName { get; init; } // for mocking #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs index c16966262c..0f3aa2cc64 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs @@ -17,10 +17,21 @@ namespace Microsoft.Generator.CSharp.Providers public abstract class TypeProvider { private Lazy _customCodeView; + private Lazy _canonicalView; + private readonly InputType? _inputType; - protected TypeProvider() + protected TypeProvider(InputType? inputType = default) { _customCodeView = new(GetCustomCodeView); + _canonicalView = new(GetCanonicalView); + _inputType = inputType; + } + + // for mocking +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + protected TypeProvider() : this(null) +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + { } private protected virtual NamedTypeSymbolProvider? GetCustomCodeView() @@ -28,6 +39,26 @@ protected TypeProvider() public NamedTypeSymbolProvider? CustomCodeView => _customCodeView.Value; + internal IReadOnlyList GetAllCustomProperties() + { + var allCustomProperties = CustomCodeView?.Properties != null + ? new List(CustomCodeView.Properties) + : []; + var baseTypeCustomCodeView = BaseTypeProvider?.CustomCodeView; + + // add all custom properties from base types + while (baseTypeCustomCodeView != null) + { + allCustomProperties.AddRange(baseTypeCustomCodeView.Properties); + baseTypeCustomCodeView = baseTypeCustomCodeView.BaseTypeProvider?.CustomCodeView; + } + + return allCustomProperties; + } + + private protected virtual CanonicalTypeProvider GetCanonicalView() => new CanonicalTypeProvider(this, _inputType); + public TypeProvider CanonicalView => _canonicalView.Value; + protected string? _deprecated; /// @@ -133,7 +164,8 @@ private TypeSignatureModifiers GetDeclarationModifiersInternal() public IReadOnlyList Implements => _implements ??= BuildImplements(); private IReadOnlyList? _properties; - public IReadOnlyList Properties => _properties ??= BuildPropertiesInternal(); + + public IReadOnlyList Properties => _properties ??= FilterCustomizedProperties(BuildProperties()); private IReadOnlyList? _methods; public IReadOnlyList Methods => _methods ??= BuildMethodsInternal(); @@ -157,64 +189,22 @@ private TypeSignatureModifiers GetDeclarationModifiersInternal() protected virtual CSharpType[] GetTypeArguments() => []; - private PropertyProvider[] BuildPropertiesInternal() + private protected virtual PropertyProvider[] FilterCustomizedProperties(PropertyProvider[] specProperties) { var properties = new List(); var customProperties = new Dictionary(); var renamedProperties = new Dictionary(); - var allCustomProperties = CustomCodeView?.Properties != null - ? new List(CustomCodeView.Properties) - : []; - var baseTypeCustomCodeView = BaseTypeProvider?.CustomCodeView; - - // add all custom properties from base types - while (baseTypeCustomCodeView != null) - { - allCustomProperties.AddRange(baseTypeCustomCodeView.Properties); - baseTypeCustomCodeView = baseTypeCustomCodeView.BaseTypeProvider?.CustomCodeView; - } - foreach (var customProperty in allCustomProperties) + foreach (var customProperty in GetAllCustomProperties()) { customProperties.Add(customProperty.Name, customProperty); - bool isRenamedProperty = false; - - foreach (var attribute in customProperty.Attributes ?? []) + if (customProperty.OriginalName != null) { - if (CodeGenAttributes.TryGetCodeGenMemberAttributeValue(attribute, out var originalName)) - { - renamedProperties.Add(originalName, customProperty); - isRenamedProperty = true; - } - } - - // Handle custom serializable properties - if (!isRenamedProperty && customProperty.WireInfo == null) - { - foreach (var attribute in GetCodeGenSerializationAttributes()) - { - if (CodeGenAttributes.TryGetCodeGenSerializationAttributeValue( - attribute, - out var propertyName, - out string? serializationName, - out _, - out _, - out _) && propertyName == customProperty.Name) - { - customProperty.WireInfo = new( - SerializationFormat.Default, - false, - !customProperty.Body.HasSetter, - customProperty.Type.IsNullable, - false, - serializationName ?? customProperty.Name.ToVariableName()); - break; - } - } + renamedProperties.Add(customProperty.OriginalName, customProperty); } } - foreach (var property in BuildProperties()) + foreach (var property in specProperties) { if (ShouldGenerate(property, customProperties, renamedProperties)) { @@ -222,7 +212,7 @@ private PropertyProvider[] BuildPropertiesInternal() } } - return properties.ToArray(); + return [..properties]; } private MethodProvider[] BuildMethodsInternal() @@ -236,7 +226,7 @@ private MethodProvider[] BuildMethodsInternal() } } - return methods.ToArray(); + return [..methods]; } private ConstructorProvider[] BuildConstructorsInternal() @@ -405,43 +395,9 @@ private bool ShouldGenerate(PropertyProvider property, IDictionary GetMemberSuppressionAttributes() => CustomCodeView?.GetAttributes()?.Where(a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenSuppressAttributeName) ?? []; - private IEnumerable GetCodeGenSerializationAttributes() - => CustomCodeView?.GetAttributes()?.Where(a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenSerializationAttributeName) ?? []; } } 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 index ac5028d74c..5dbaecfa82 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/TypeFactory.cs @@ -105,34 +105,34 @@ protected internal TypeFactory() /// /// The to convert. /// An instance of . - private CSharpType CreatePrimitiveCSharpTypeCore(InputType inputType) => inputType switch + internal static Type CreatePrimitiveCSharpTypeCore(InputType inputType) => inputType switch { InputPrimitiveType primitiveType => primitiveType.Kind switch { - InputPrimitiveTypeKind.Boolean => new CSharpType(typeof(bool)), - InputPrimitiveTypeKind.Bytes => new CSharpType(typeof(BinaryData)), - InputPrimitiveTypeKind.PlainDate => new CSharpType(typeof(DateTimeOffset)), - InputPrimitiveTypeKind.Decimal => new CSharpType(typeof(decimal)), - InputPrimitiveTypeKind.Decimal128 => new CSharpType(typeof(decimal)), - InputPrimitiveTypeKind.PlainTime => new CSharpType(typeof(TimeSpan)), - InputPrimitiveTypeKind.Float32 => new CSharpType(typeof(float)), - InputPrimitiveTypeKind.Float64 => new CSharpType(typeof(double)), - InputPrimitiveTypeKind.Int8 => new CSharpType(typeof(sbyte)), - InputPrimitiveTypeKind.UInt8 => new CSharpType(typeof(byte)), - InputPrimitiveTypeKind.Int32 => new CSharpType(typeof(int)), - InputPrimitiveTypeKind.Int64 => new CSharpType(typeof(long)), - InputPrimitiveTypeKind.SafeInt => new CSharpType(typeof(long)), - InputPrimitiveTypeKind.Integer => new CSharpType(typeof(long)), // in typespec, integer is the base type of int related types, see type relation: https://typespec.io/docs/language-basics/type-relations - InputPrimitiveTypeKind.Float => new CSharpType(typeof(double)), // in typespec, float is the base type of float32 and float64, see type relation: https://typespec.io/docs/language-basics/type-relations - InputPrimitiveTypeKind.Numeric => new CSharpType(typeof(double)), // in typespec, numeric is the base type of number types, see type relation: https://typespec.io/docs/language-basics/type-relations - InputPrimitiveTypeKind.Stream => new CSharpType(typeof(Stream)), - InputPrimitiveTypeKind.String => new CSharpType(typeof(string)), - InputPrimitiveTypeKind.Url => new CSharpType(typeof(Uri)), - InputPrimitiveTypeKind.Unknown => new CSharpType(typeof(BinaryData)), - _ => new CSharpType(typeof(object)), + InputPrimitiveTypeKind.Boolean => typeof(bool), + InputPrimitiveTypeKind.Bytes => typeof(BinaryData), + InputPrimitiveTypeKind.PlainDate => typeof(DateTimeOffset), + InputPrimitiveTypeKind.Decimal => typeof(decimal), + InputPrimitiveTypeKind.Decimal128 => typeof(decimal), + InputPrimitiveTypeKind.PlainTime => typeof(TimeSpan), + InputPrimitiveTypeKind.Float32 => typeof(float), + InputPrimitiveTypeKind.Float64 => typeof(double), + InputPrimitiveTypeKind.Int8 => typeof(sbyte), + InputPrimitiveTypeKind.UInt8 => typeof(byte), + InputPrimitiveTypeKind.Int32 => typeof(int), + InputPrimitiveTypeKind.Int64 => typeof(long), + InputPrimitiveTypeKind.SafeInt => typeof(long), + InputPrimitiveTypeKind.Integer => typeof(long), // in typespec, integer is the base type of int related types, see type relation: https://typespec.io/docs/language-basics/type-relations + InputPrimitiveTypeKind.Float => typeof(double), // in typespec, float is the base type of float32 and float64, see type relation: https://typespec.io/docs/language-basics/type-relations + InputPrimitiveTypeKind.Numeric => typeof(double), // in typespec, numeric is the base type of number types, see type relation: https://typespec.io/docs/language-basics/type-relations + InputPrimitiveTypeKind.Stream => typeof(Stream), + InputPrimitiveTypeKind.String => typeof(string), + InputPrimitiveTypeKind.Url => typeof(Uri), + InputPrimitiveTypeKind.Unknown => typeof(BinaryData), + _ => typeof(object), }, - InputDateTimeType dateTimeType => new CSharpType(typeof(DateTimeOffset)), - InputDurationType durationType => new CSharpType(typeof(TimeSpan)), + InputDateTimeType dateTimeType => typeof(DateTimeOffset), + InputDurationType durationType => typeof(TimeSpan), _ => throw new InvalidOperationException($"Unknown type: {inputType}") }; diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs index 1efa2c8704..d6c3613e9f 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.Generator.CSharp.Primitives; @@ -11,6 +12,7 @@ namespace Microsoft.Generator.CSharp internal static class TypeSymbolExtensions { private const string GlobalPrefix = "global::"; + private const string NullableTypeName = "System.Nullable"; public static bool IsSameType(this INamedTypeSymbol symbol, CSharpType type) { @@ -110,18 +112,19 @@ public static string GetFullyQualifiedName(this ITypeSymbol typeSymbol) // Handle nullable types if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated && !IsCollectionType(namedTypeSymbol)) { - const string nullableTypeName = "System.Nullable"; var argTypeSymbol = namedTypeSymbol.TypeArguments.FirstOrDefault(); if (argTypeSymbol != null) { + // If the argument type is an error type, then fall back to using the ToString of the arg type symbol. This means that the + // arg may not be fully qualified, but it is better than not having any type information at all. if (argTypeSymbol.TypeKind == TypeKind.Error) { - return GetFullyQualifiedName(argTypeSymbol); + return $"{NullableTypeName}`1[{argTypeSymbol}]"; } string[] typeArguments = [.. namedTypeSymbol.TypeArguments.Select(arg => "[" + GetFullyQualifiedName(arg) + "]")]; - return $"{nullableTypeName}`{namedTypeSymbol.TypeArguments.Length}[{string.Join(", ", typeArguments)}]"; + return $"{NullableTypeName}`{namedTypeSymbol.TypeArguments.Length}[{string.Join(", ", typeArguments)}]"; } } else if (namedTypeSymbol.TypeArguments.Length > 0 && !IsCollectionType(namedTypeSymbol)) @@ -152,7 +155,7 @@ public static string GetFullyQualifiedNameFromDisplayString(this ISymbol typeSym { // Special case for types that would not be defined in corlib, but should still be considered framework types. "System.BinaryData" => typeof(BinaryData), - _ => System.Type.GetType(fullyQualifiedName) + _ => Type.GetType(fullyQualifiedName) }; } @@ -163,18 +166,28 @@ private static CSharpType ConstructCSharpTypeFromSymbol( { var typeArg = namedTypeSymbol?.TypeArguments.FirstOrDefault(); bool isValueType = typeSymbol.IsValueType; - bool isEnum = typeSymbol.TypeKind == TypeKind.Enum; - bool isNullable = typeSymbol.NullableAnnotation == NullableAnnotation.Annotated; + bool isNullable = fullyQualifiedName.StartsWith(NullableTypeName); + bool isEnum = typeSymbol.TypeKind == TypeKind.Enum || (isNullable && typeArg?.TypeKind == TypeKind.Enum); bool isNullableUnknownType = isNullable && typeArg?.TypeKind == TypeKind.Error; string name = isNullableUnknownType ? fullyQualifiedName : typeSymbol.Name; string[] pieces = fullyQualifiedName.Split('.'); + List arguments = []; + INamedTypeSymbol? namedTypeArg = typeArg as INamedTypeSymbol; + INamedTypeSymbol? enumUnderlyingType = !isNullable ? namedTypeSymbol?.EnumUnderlyingType : namedTypeArg?.EnumUnderlyingType; + + // For nullable types, we need to get the type arguments from the underlying type. + if (namedTypeSymbol?.IsGenericType == true && + (!isNullable || (namedTypeArg?.IsGenericType == true))) + { + arguments.AddRange(namedTypeSymbol.TypeArguments.Select(GetCSharpType)); + } // handle nullables - if (isNullable) + if (isNullable && typeArg != null) { // System.Nullable`1[T] -> T - name = typeArg != null ? GetFullyQualifiedName(typeArg) : fullyQualifiedName; - pieces = name.Split('.'); + name = typeArg.Name; + pieces = GetFullyQualifiedName(typeArg).Split('.'); } return new CSharpType( @@ -183,14 +196,14 @@ private static CSharpType ConstructCSharpTypeFromSymbol( isValueType, isNullable, typeSymbol.ContainingType is not null ? GetCSharpType(typeSymbol.ContainingType) : null, - namedTypeSymbol is not null && !isNullableUnknownType ? [.. namedTypeSymbol.TypeArguments.Select(GetCSharpType)] : [], + arguments, typeSymbol.DeclaredAccessibility == Accessibility.Public, isValueType && !isEnum, baseType: typeSymbol.BaseType is not null && typeSymbol.BaseType.TypeKind != TypeKind.Error && !isNullableUnknownType ? GetCSharpType(typeSymbol.BaseType) : null, - underlyingEnumType: namedTypeSymbol is not null && namedTypeSymbol.EnumUnderlyingType is not null - ? GetCSharpType(namedTypeSymbol.EnumUnderlyingType).FrameworkType + underlyingEnumType: enumUnderlyingType != null + ? GetCSharpType(enumUnderlyingType).FrameworkType : null); } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/CanonicalTypeProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/CanonicalTypeProviderTests.cs new file mode 100644 index 0000000000..da9b9301f8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/CanonicalTypeProviderTests.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.Generator.CSharp.Input; +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.Providers; +using Microsoft.Generator.CSharp.Tests.Providers.NamedTypeSymbolProviders; +using NUnit.Framework; + +namespace Microsoft.Generator.CSharp.Tests.Providers +{ + public class CanonicalTypeProviderTests + { + private NamedTypeSymbolProvider _namedTypeSymbolProvider; + private NamedSymbol _namedSymbol; + private readonly TestTypeProvider _typeProvider; + private readonly Compilation _compilation; + + public CanonicalTypeProviderTests() + { + _namedSymbol = new NamedSymbol(name: "TestName"); + _compilation = CompilationHelper.LoadCompilation([_namedSymbol, new PropertyType()]); + var iNamedSymbol = CompilationHelper.GetSymbol(_compilation.Assembly.Modules.First().GlobalNamespace, "TestName"); + + _namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!); + _typeProvider = new TestTypeProvider(); + } + + [SetUp] + public async Task Setup() + { + await MockHelpers.LoadMockPluginAsync(compilation: () => Task.FromResult(_compilation)); + } + + [Test] + public void ValidateModifiers() + { + var modifiers = _typeProvider.CanonicalView.DeclarationModifiers; + Assert.IsTrue(modifiers.HasFlag(TypeSignatureModifiers.Internal | TypeSignatureModifiers.Partial | TypeSignatureModifiers.Class)); + } + + [Test] + public void ValidateName() + { + Assert.AreEqual(_typeProvider.Name, _typeProvider.CanonicalView.Name); + } + + [Test] + public void ValidateNamespace() + { + Assert.AreEqual(_typeProvider.Namespace, _typeProvider.CanonicalView.Namespace); + } + + [Test] + public void ValidateProperties() + { + Dictionary properties = _typeProvider.CanonicalView.Properties.ToDictionary(p => p.Name); + Assert.AreEqual(5, properties.Count); + Assert.AreEqual(1, _typeProvider.Properties.Count); + Assert.AreEqual(4, _typeProvider.CustomCodeView!.Properties.Count); + foreach (var expected in _namedSymbol.Properties) + { + var actual = properties[expected.Name]; + + Assert.IsTrue(properties.ContainsKey(expected.Name)); + Assert.AreEqual(expected.Name, actual.Name); + Assert.AreEqual($"{expected.Description}.", actual.Description.ToString()); // the writer adds a period + Assert.AreEqual(expected.Modifiers, actual.Modifiers); + Assert.AreEqual(expected.Type, actual.Type); + Assert.AreEqual(expected.Body.GetType(), actual.Body.GetType()); + Assert.AreEqual(expected.Body.HasSetter, actual.Body.HasSetter); + } + } + + + private class TestTypeProvider : TypeProvider + { + protected override string BuildRelativeFilePath() => "NamedSymbol"; + + protected override string BuildName() => "TestName"; + + protected override string GetNamespace() => CodeModelPlugin.Instance.Configuration.ModelNamespace; + + protected override TypeSignatureModifiers GetDeclarationModifiers() => TypeSignatureModifiers.Internal | TypeSignatureModifiers.Partial |TypeSignatureModifiers.Class; + + protected override PropertyProvider[] BuildProperties() + { + return + [ + // customized by the NamedSymbol + new PropertyProvider($"Foo property", MethodSignatureModifiers.Public, typeof(int), "IntProperty", new AutoPropertyBody(true), this, wireInfo: new PropertyWireInformation(SerializationFormat.Default, true, true, true, false, "intProperty")), + // not customized by the NamedSymbol + new PropertyProvider($"Bar property", MethodSignatureModifiers.Public, typeof(string), "SpecProperty", new AutoPropertyBody(false), this, wireInfo: new PropertyWireInformation(SerializationFormat.Default, true, true, true, false, "stringProperty")), + ]; + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs index 26136558eb..39dca8e7b7 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs @@ -36,7 +36,7 @@ public async Task CanChangePropertyName() { var props = new[] { - InputFactory.Property("Prop1", InputFactory.Array(InputPrimitiveType.String)) + InputFactory.Property("prop1", InputFactory.Array(InputPrimitiveType.String)) }; var inputModel = InputFactory.Model("mockInputModel", properties: props); @@ -57,6 +57,10 @@ public async Task CanChangePropertyName() Assert.AreEqual( "prop1", wireInfo!.SerializedName); Assert.AreEqual(0, modelTypeProvider.Properties.Count); + + var fullCtor = modelTypeProvider.Constructors.Last(); + Assert.IsTrue(fullCtor.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal)); + Assert.AreEqual(2, fullCtor.Signature.Parameters.Count); } [Test] @@ -64,7 +68,7 @@ public async Task CanChangePropertyNameAndRedefineOriginal() { var props = new[] { - InputFactory.Property("Prop1", InputFactory.Array(InputPrimitiveType.String)) + InputFactory.Property("prop1", InputFactory.Array(InputPrimitiveType.String)) }; var inputModel = InputFactory.Model("mockInputModel", properties: props); @@ -94,7 +98,7 @@ public async Task CanChangePropertyType() { var props = new[] { - InputFactory.Property("Prop1", InputFactory.Array(InputPrimitiveType.String)) + InputFactory.Property("prop1", InputFactory.Array(InputPrimitiveType.String)) }; var inputModel = InputFactory.Model("mockInputModel", properties: props); @@ -112,13 +116,37 @@ public async Task CanChangePropertyType() Assert.AreEqual(new CSharpType(typeof(int[])), modelTypeProvider.CustomCodeView.Properties[0].Type); } + [Test] + public async Task CanChangePropertyTypeToEnum() + { + var props = new[] + { + InputFactory.Property("Prop1", InputPrimitiveType.String) + }; + + var inputModel = InputFactory.Model("mockInputModel", properties: props); + + var plugin = await MockHelpers.LoadMockPluginAsync( + inputModelTypes: [inputModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var modelTypeProvider = plugin.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel"); + AssertCommon(modelTypeProvider, "Sample.Models", "MockInputModel"); + + // the property should be added to the custom code view + Assert.AreEqual(1, modelTypeProvider.CustomCodeView!.Properties.Count); + // the property type should be changed + Assert.AreEqual("SomeEnum", modelTypeProvider.CustomCodeView.Properties[0].Type.Name); + Assert.IsTrue(modelTypeProvider.CustomCodeView.Properties[0].Type.IsNullable); + } + [Test] public async Task CanChangePropertyAccessibility() { var plugin = await MockHelpers.LoadMockPluginAsync( inputModelTypes: new[] { InputFactory.Model("mockInputModel", properties: new[] { - InputFactory.Property("Prop1", InputPrimitiveType.String) + InputFactory.Property("prop1", InputPrimitiveType.String) }) }, compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangePropertyTypeToEnum/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangePropertyTypeToEnum/MockInputModel.cs new file mode 100644 index 0000000000..6b90385402 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangePropertyTypeToEnum/MockInputModel.cs @@ -0,0 +1,19 @@ +#nullable disable + +using Sample; +using Microsoft.Generator.CSharp.Customization; + +namespace Sample.Models +{ + public partial class MockInputModel + { + // CUSTOM: Changed type from string. + [CodeGenMember("Prop1")] + public SomeEnum? Prop1 { get; } + } + + public enum SomeEnum + { + Foo, + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs index 2d996ebf02..d99d050684 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs @@ -4,12 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; using Microsoft.Generator.CSharp.Primitives; using Microsoft.Generator.CSharp.Providers; using NUnit.Framework; -using static Microsoft.Generator.CSharp.Snippets.Snippet; namespace Microsoft.Generator.CSharp.Tests.Providers.NamedTypeSymbolProviders { @@ -22,7 +19,7 @@ public NamedTypeSymbolProviderTests() { _namedSymbol = new NamedSymbol(); var compilation = CompilationHelper.LoadCompilation([_namedSymbol, new PropertyType()]); - var iNamedSymbol = GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "NamedSymbol"); + var iNamedSymbol = CompilationHelper.GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "NamedSymbol"); _namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!); } @@ -74,6 +71,7 @@ public void ValidateProperties() [TestCase(typeof(IList))] [TestCase(typeof(IList))] [TestCase(typeof(IList))] + [TestCase(typeof(IList))] [TestCase(typeof(ReadOnlyMemory?))] [TestCase(typeof(ReadOnlyMemory))] [TestCase(typeof(ReadOnlyMemory))] @@ -83,13 +81,16 @@ public void ValidateProperties() [TestCase(typeof(string[]))] [TestCase(typeof(IDictionary))] [TestCase(typeof(BinaryData))] - public void ValidatePropertyTypes(Type propertyType) + [TestCase(typeof(SomeEnum), true)] + [TestCase(typeof(SomeEnum?), true)] + [TestCase(typeof(IDictionary))] + public void ValidatePropertyTypes(Type propertyType, bool isEnum = false) { // setup var namedSymbol = new NamedSymbol(propertyType); _namedSymbol = namedSymbol; var compilation = CompilationHelper.LoadCompilation([namedSymbol, new PropertyType()]); - var iNamedSymbol = GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "NamedSymbol"); + var iNamedSymbol = CompilationHelper.GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "NamedSymbol"); _namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!); @@ -98,9 +99,15 @@ public void ValidatePropertyTypes(Type propertyType) var property = _namedTypeSymbolProvider.Properties.FirstOrDefault(); Assert.IsNotNull(property); - bool isNullable = Nullable.GetUnderlyingType(propertyType) != null; - var expectedType = propertyType.FullName!.StartsWith("System") ? new CSharpType(propertyType, isNullable) : - new CSharpType(propertyType.Name, propertyType.Namespace!, false, isNullable, null, [], false, false); + Type? nullableUnderlyingType = Nullable.GetUnderlyingType(propertyType); + var propertyName = nullableUnderlyingType?.Name ?? propertyType.Name; + bool isNullable = nullableUnderlyingType != null; + bool isSystemType = propertyType.FullName!.StartsWith("System") + && (!isNullable || nullableUnderlyingType?.Namespace?.StartsWith("System") == true); + + var expectedType = isSystemType + ? new CSharpType(propertyType, isNullable) + : new CSharpType(propertyName, propertyType.Namespace!, false, isNullable, null, [], false, false); var propertyCSharpType = property!.Type; @@ -192,108 +199,9 @@ public void ValidateFields() } } - private class NamedSymbol : TypeProvider + public enum SomeEnum { - private readonly Type? _propertyType; - protected override string BuildRelativeFilePath() => "."; - - protected override string BuildName() => "NamedSymbol"; - - protected override string GetNamespace() => CodeModelPlugin.Instance.Configuration.ModelNamespace; - - public NamedSymbol(Type? propertyType = null) : base() - { - _propertyType = propertyType; - } - - protected override FieldProvider[] BuildFields() - { - return - [ - new FieldProvider(FieldModifiers.Public, typeof(int), "IntField", new TestTypeProvider(), $"PublicIntField field"), - new FieldProvider(FieldModifiers.Private, typeof(string), "StringField", new TestTypeProvider(), $"PrivateStringField field no setter"), - new FieldProvider(FieldModifiers.Internal, typeof(double), "DoubleField", new TestTypeProvider(), $"InternalDoubleField field"), - new FieldProvider(FieldModifiers.Public | FieldModifiers.Static, typeof(float), "FloatField", new TestTypeProvider(), $"PublicStaticFloatField field"), - ]; - } - - protected override PropertyProvider[] BuildProperties() - { - if (_propertyType == null) - { - return - [ - new PropertyProvider($"IntProperty property", MethodSignatureModifiers.Public, typeof(int), "IntProperty", new AutoPropertyBody(true), this), - new PropertyProvider($"StringProperty property no setter", MethodSignatureModifiers.Public, typeof(string), "StringProperty", new AutoPropertyBody(false), this), - new PropertyProvider($"InternalStringProperty property no setter", MethodSignatureModifiers.Public, typeof(string), "InternalStringProperty", new AutoPropertyBody(false), this), - new PropertyProvider($"PropertyTypeProperty property", MethodSignatureModifiers.Public, new PropertyType().Type, "PropertyTypeProperty", new AutoPropertyBody(true), this), - ]; - } - - return - [ - new PropertyProvider($"p1", MethodSignatureModifiers.Public, _propertyType, "P1", new AutoPropertyBody(true), this) - ]; - } - - protected override ConstructorProvider[] BuildConstructors() - { - var intParam = new ParameterProvider("intParam", $"intParam", new CSharpType(typeof(int))); - - return - [ - new ConstructorProvider( - new ConstructorSignature(Type, $"Initializes a new instance of {Type}", MethodSignatureModifiers.Public, [intParam]), - Throw(New.Instance(typeof(NotImplementedException))), - this) - ]; - } - - protected override MethodProvider[] BuildMethods() - { - var intParam = new ParameterProvider("intParam", $"intParam", new CSharpType(typeof(int))); - - return - [ - new MethodProvider( - new MethodSignature("Method1", $"Description of method1", MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, typeof(Task), null, [intParam]), - Throw(New.Instance(typeof(NotImplementedException))), - this) - ]; - } - } - - private class PropertyType : TypeProvider - { - protected override PropertyProvider[] BuildProperties() - { - return - [ - new PropertyProvider($"Foo property", MethodSignatureModifiers.Public, typeof(int), "Foo", new AutoPropertyBody(true), this), - ]; - } - - protected override string BuildRelativeFilePath() => "."; - - protected override string BuildName() => "PropertyType"; - } - - internal static INamedTypeSymbol? GetSymbol(INamespaceSymbol namespaceSymbol, string name) - { - foreach (var childNamespaceSymbol in namespaceSymbol.GetNamespaceMembers()) - { - return GetSymbol(childNamespaceSymbol, name); - } - - foreach (INamedTypeSymbol symbol in namespaceSymbol.GetTypeMembers()) - { - if (symbol.MetadataName == name) - { - return symbol; - } - } - - return null; + Foo, } } } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestNamedSymbol.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestNamedSymbol.cs new file mode 100644 index 0000000000..4a065909e5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestNamedSymbol.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.Providers; +using Microsoft.Generator.CSharp.Snippets; +using static Microsoft.Generator.CSharp.Snippets.Snippet; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class NamedSymbol : TypeProvider + { + private readonly Type? _propertyType; + private readonly string _typeName; + protected override string BuildRelativeFilePath() => "."; + + protected override string BuildName() => _typeName; + + protected override string GetNamespace() => CodeModelPlugin.Instance.Configuration.ModelNamespace; + + public NamedSymbol(Type? propertyType = null, string name = "NamedSymbol") + { + _propertyType = propertyType; + _typeName = name; + } + + protected override FieldProvider[] BuildFields() + { + return + [ + new FieldProvider(FieldModifiers.Public, typeof(int), "IntField", new TestTypeProvider(), + $"PublicIntField field"), + new FieldProvider(FieldModifiers.Private, typeof(string), "StringField", new TestTypeProvider(), + $"PrivateStringField field no setter"), + new FieldProvider(FieldModifiers.Internal, typeof(double), "DoubleField", new TestTypeProvider(), + $"InternalDoubleField field"), + new FieldProvider(FieldModifiers.Public | FieldModifiers.Static, typeof(float), "FloatField", + new TestTypeProvider(), $"PublicStaticFloatField field"), + ]; + } + + protected override PropertyProvider[] BuildProperties() + { + if (_propertyType == null) + { + return + [ + new PropertyProvider($"IntProperty property", MethodSignatureModifiers.Public, typeof(int), + "IntProperty", new AutoPropertyBody(true), this), + new PropertyProvider($"StringProperty property no setter", MethodSignatureModifiers.Public, + typeof(string), "StringProperty", new AutoPropertyBody(false), this), + new PropertyProvider($"InternalStringProperty property no setter", MethodSignatureModifiers.Public, + typeof(string), "InternalStringProperty", new AutoPropertyBody(false), this), + new PropertyProvider($"PropertyTypeProperty property", MethodSignatureModifiers.Public, + new PropertyType().Type, "PropertyTypeProperty", new AutoPropertyBody(true), this), + ]; + } + + return + [ + new PropertyProvider($"p1", MethodSignatureModifiers.Public, _propertyType, "P1", + new AutoPropertyBody(true), this) + ]; + } + + protected override ConstructorProvider[] BuildConstructors() + { + var intParam = new ParameterProvider("intParam", $"intParam", new CSharpType(typeof(int))); + + return + [ + new ConstructorProvider( + new ConstructorSignature(Type, $"Initializes a new instance of {Type}", + MethodSignatureModifiers.Public, [intParam]), + Throw(Snippet.New.Instance(typeof(NotImplementedException))), + this) + ]; + } + + protected override MethodProvider[] BuildMethods() + { + var intParam = new ParameterProvider("intParam", $"intParam", new CSharpType(typeof(int))); + + return + [ + new MethodProvider( + new MethodSignature("Method1", $"Description of method1", + MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, typeof(Task), null, + [intParam]), + Throw(Snippet.New.Instance(typeof(NotImplementedException))), + this) + ]; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestPropertyType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestPropertyType.cs new file mode 100644 index 0000000000..52199b9bfe --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestPropertyType.cs @@ -0,0 +1,20 @@ +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.Providers; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class PropertyType : TypeProvider + { + protected override PropertyProvider[] BuildProperties() + { + return + [ + new PropertyProvider($"Foo property", MethodSignatureModifiers.Public, typeof(int), "Foo", new AutoPropertyBody(true), this), + ]; + } + + protected override string BuildRelativeFilePath() => "."; + + protected override string BuildName() => "PropertyType"; + } +} diff --git a/packages/http-specs/package.json b/packages/http-specs/package.json index 37e847caa0..d41d60a276 100644 --- a/packages/http-specs/package.json +++ b/packages/http-specs/package.json @@ -49,7 +49,6 @@ "@typespec/compiler": "workspace:~", "@typespec/http": "workspace:~", "@typespec/rest": "workspace:~", - "@typespec/spec-lib": "workspace:~", "@typespec/versioning": "workspace:~", "@typespec/xml": "workspace:~" } diff --git a/packages/http-specs/specs/authentication/api-key/main.tsp b/packages/http-specs/specs/authentication/api-key/main.tsp index 8cade48fd3..e7ee63add6 100644 --- a/packages/http-specs/specs/authentication/api-key/main.tsp +++ b/packages/http-specs/specs/authentication/api-key/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/authentication/api-key") @doc("Illustrates clients generated with ApiKey authentication.") diff --git a/packages/http-specs/specs/authentication/api-key/mockapi.ts b/packages/http-specs/specs/authentication/api-key/mockapi.ts index ac41373a27..2d8bfd8073 100644 --- a/packages/http-specs/specs/authentication/api-key/mockapi.ts +++ b/packages/http-specs/specs/authentication/api-key/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -17,14 +17,6 @@ Scenarios.Authentication_ApiKey_invalid = passOnSuccess({ error: "invalid-api-key", }), }, - handler: (req: MockRequest) => { - return { - status: 403, - body: json({ - error: "invalid-api-key", - }), - }; - }, kind: "MockApiDefinition", }); @@ -39,9 +31,5 @@ Scenarios.Authentication_ApiKey_valid = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-api-key", "valid-key"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/authentication/http/custom/main.tsp b/packages/http-specs/specs/authentication/http/custom/main.tsp index 5d666cecba..6165727803 100644 --- a/packages/http-specs/specs/authentication/http/custom/main.tsp +++ b/packages/http-specs/specs/authentication/http/custom/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using TypeSpec.Http; -using SpecLib; +using Spector; @scenarioService("/authentication/http/custom") @doc("Illustrates clients generated with generic HTTP auth.") diff --git a/packages/http-specs/specs/authentication/http/custom/mockapi.ts b/packages/http-specs/specs/authentication/http/custom/mockapi.ts index 882f64ee20..9ebb14b972 100644 --- a/packages/http-specs/specs/authentication/http/custom/mockapi.ts +++ b/packages/http-specs/specs/authentication/http/custom/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Authentication_Http_Custom_valid = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("authorization", "SharedAccessKey valid-key"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -35,13 +31,5 @@ Scenarios.Authentication_Http_Custom_invalid = passOnSuccess({ error: "invalid-api-key", }), }, - handler: (req: MockRequest) => { - return { - status: 403, - body: json({ - error: "invalid-api-key", - }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/authentication/oauth2/main.tsp b/packages/http-specs/specs/authentication/oauth2/main.tsp index ef6fa1ec65..ced1738a65 100644 --- a/packages/http-specs/specs/authentication/oauth2/main.tsp +++ b/packages/http-specs/specs/authentication/oauth2/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/authentication/oauth2") @doc("Illustrates clients generated with OAuth2 authentication.") diff --git a/packages/http-specs/specs/authentication/oauth2/mockapi.ts b/packages/http-specs/specs/authentication/oauth2/mockapi.ts index acc244e29d..85fcaf576c 100644 --- a/packages/http-specs/specs/authentication/oauth2/mockapi.ts +++ b/packages/http-specs/specs/authentication/oauth2/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Authentication_OAuth2_valid = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("authorization", "Bearer https://security.microsoft.com/.default"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -32,13 +28,5 @@ Scenarios.Authentication_OAuth2_invalid = passOnSuccess({ error: "invalid-grant", }), }, - handler: (req: MockRequest) => { - return { - status: 403, - body: json({ - error: "invalid-grant", - }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/authentication/union/main.tsp b/packages/http-specs/specs/authentication/union/main.tsp index 17873d352b..9d6afef5c8 100644 --- a/packages/http-specs/specs/authentication/union/main.tsp +++ b/packages/http-specs/specs/authentication/union/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/authentication/union") @doc("Illustrates clients generated with ApiKey and OAuth2 authentication.") diff --git a/packages/http-specs/specs/authentication/union/mockapi.ts b/packages/http-specs/specs/authentication/union/mockapi.ts index 1efed8a672..e52e327ec3 100644 --- a/packages/http-specs/specs/authentication/union/mockapi.ts +++ b/packages/http-specs/specs/authentication/union/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Authentication_Union_validKey = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-api-key", "valid-key"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -31,9 +27,5 @@ Scenarios.Authentication_Union_validToken = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("authorization", "Bearer https://security.microsoft.com/.default"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/encode/bytes/main.tsp b/packages/http-specs/specs/encode/bytes/main.tsp index 69b6cc1fd5..e69ed6e194 100644 --- a/packages/http-specs/specs/encode/bytes/main.tsp +++ b/packages/http-specs/specs/encode/bytes/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for encode decorator on bytes.") @scenarioService("/encode/bytes") diff --git a/packages/http-specs/specs/encode/bytes/mockapi.ts b/packages/http-specs/specs/encode/bytes/mockapi.ts index 3840b5ba17..f267462203 100644 --- a/packages/http-specs/specs/encode/bytes/mockapi.ts +++ b/packages/http-specs/specs/encode/bytes/mockapi.ts @@ -1,11 +1,5 @@ import { resolvePath } from "@typespec/compiler"; -import { - CollectionFormat, - json, - MockRequest, - passOnSuccess, - ScenarioMockApi, -} from "@typespec/spec-api"; +import { CollectionFormat, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; import { readFileSync } from "fs"; import { fileURLToPath } from "url"; @@ -78,13 +72,6 @@ function createPropertyServerTests(uri: string, data: any, value: any) { response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals({ value: value }); - return { - status: 200, - body: json({ value: value }), - }; - }, kind: "MockApiDefinition", }); } @@ -126,12 +113,6 @@ function createHeaderServerTests(uri: string, data: any, value: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("value", value); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); } @@ -208,11 +189,11 @@ Scenarios.Encode_Bytes_RequestBody_base64 = createRequestBodyServerTests( ); Scenarios.Encode_Bytes_RequestBody_base64url = createRequestBodyServerTests( "/encode/bytes/body/request/base64url", - '"dGVzdA=="', + '"dGVzdA"', { "Content-Type": "application/json", }, - '"dGVzdA=="', + '"dGVzdA"', ); Scenarios.Encode_Bytes_RequestBody_customContentType = createRequestBodyServerTests( diff --git a/packages/http-specs/specs/encode/datetime/main.tsp b/packages/http-specs/specs/encode/datetime/main.tsp index 2a4abf5433..52ba484f23 100644 --- a/packages/http-specs/specs/encode/datetime/main.tsp +++ b/packages/http-specs/specs/encode/datetime/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for encode decorator on datetime.") @scenarioService("/encode/datetime") diff --git a/packages/http-specs/specs/encode/duration/main.tsp b/packages/http-specs/specs/encode/duration/main.tsp index 3f06ad10b5..9509745119 100644 --- a/packages/http-specs/specs/encode/duration/main.tsp +++ b/packages/http-specs/specs/encode/duration/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for encode decorator on duration.") @scenarioService("/encode/duration") diff --git a/packages/http-specs/specs/encode/duration/mockapi.ts b/packages/http-specs/specs/encode/duration/mockapi.ts index 6008f9ad0a..c6c5413a0c 100644 --- a/packages/http-specs/specs/encode/duration/mockapi.ts +++ b/packages/http-specs/specs/encode/duration/mockapi.ts @@ -19,13 +19,6 @@ function createBodyServerTests(uri: string, data: any, value: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals({ value: value }); - return { - status: 200, - body: json({ value: value }), - }; - }, kind: "MockApiDefinition", }); } @@ -150,12 +143,6 @@ function createHeaderServerTests(uri: string, headersData: any, value: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("duration", value); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); } @@ -177,28 +164,21 @@ Scenarios.Encode_Duration_Header_iso8601 = createHeaderServerTests( Scenarios.Encode_Duration_Header_int32Seconds = createHeaderServerTests( "/encode/duration/header/int32-seconds", { - duration: 36, + duration: "36", }, "36", ); Scenarios.Encode_Duration_Header_floatSeconds = createHeaderServerTests( "/encode/duration/header/float-seconds", { - duration: 35.625, - }, - "35.625", -); -Scenarios.Encode_Duration_Header_floatSeconds = createHeaderServerTests( - "/encode/duration/header/float64-seconds", - { - duration: 35.625, + duration: "35.625", }, "35.625", ); Scenarios.Encode_Duration_Header_float64Seconds = createHeaderServerTests( "/encode/duration/header/float64-seconds", { - duration: 35.625, + duration: "35.625", }, "35.625", ); diff --git a/packages/http-specs/specs/encode/numeric/main.tsp b/packages/http-specs/specs/encode/numeric/main.tsp index ee2099e572..5677d2c673 100644 --- a/packages/http-specs/specs/encode/numeric/main.tsp +++ b/packages/http-specs/specs/encode/numeric/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for encode decorator on integer.") @scenarioService("/encode/numeric") diff --git a/packages/http-specs/specs/encode/numeric/mockapi.ts b/packages/http-specs/specs/encode/numeric/mockapi.ts index 80292c88a4..42814aa5a0 100644 --- a/packages/http-specs/specs/encode/numeric/mockapi.ts +++ b/packages/http-specs/specs/encode/numeric/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -15,13 +15,6 @@ function createTests(uri: string, value: any) { status: 200, body: json({ value }), }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals({ value }); - return { - status: 200, - body: json({ value }), - }; - }, kind: "MockApiDefinition", }); } diff --git a/packages/http-specs/specs/parameters/basic/main.tsp b/packages/http-specs/specs/parameters/basic/main.tsp index 65f766f1a9..d797a05cec 100644 --- a/packages/http-specs/specs/parameters/basic/main.tsp +++ b/packages/http-specs/specs/parameters/basic/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for basic parameters cases.") @scenarioService("/parameters/basic") diff --git a/packages/http-specs/specs/parameters/basic/mockapi.ts b/packages/http-specs/specs/parameters/basic/mockapi.ts index 509cce0aa4..8cd105bca0 100644 --- a/packages/http-specs/specs/parameters/basic/mockapi.ts +++ b/packages/http-specs/specs/parameters/basic/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; function createServerTests(uri: string) { @@ -13,10 +13,6 @@ function createServerTests(uri: string) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); } diff --git a/packages/http-specs/specs/parameters/body-optionality/main.tsp b/packages/http-specs/specs/parameters/body-optionality/main.tsp index 32d9acf9d1..1019638fc7 100644 --- a/packages/http-specs/specs/parameters/body-optionality/main.tsp +++ b/packages/http-specs/specs/parameters/body-optionality/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test describing optionality of the request body.") @scenarioService("/parameters/body-optionality") diff --git a/packages/http-specs/specs/parameters/body-optionality/mockapi.ts b/packages/http-specs/specs/parameters/body-optionality/mockapi.ts index 0e7e2a31ac..8cefc38e52 100644 --- a/packages/http-specs/specs/parameters/body-optionality/mockapi.ts +++ b/packages/http-specs/specs/parameters/body-optionality/mockapi.ts @@ -11,10 +11,6 @@ function createServerTests(uri: string, data: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); } @@ -38,25 +34,17 @@ Scenarios.Parameters_BodyOptionality_OptionalExplicit = passOnSuccess([ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }, { uri: "/parameters/body-optionality/optional-explicit/omit", method: "post", - request: { - body: { - name: "foo", - }, - }, + request: {}, response: { status: 204, }, handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); + req.expect.rawBodyEquals(undefined); return { status: 204 }; }, kind: "MockApiDefinition", diff --git a/packages/http-specs/specs/parameters/collection-format/main.tsp b/packages/http-specs/specs/parameters/collection-format/main.tsp index 355b295901..dc420a49f9 100644 --- a/packages/http-specs/specs/parameters/collection-format/main.tsp +++ b/packages/http-specs/specs/parameters/collection-format/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for collectionFormat.") @scenarioService("/parameters/collection-format") diff --git a/packages/http-specs/specs/parameters/collection-format/mockapi.ts b/packages/http-specs/specs/parameters/collection-format/mockapi.ts index 60f97a1362..5b5bdc1b23 100644 --- a/packages/http-specs/specs/parameters/collection-format/mockapi.ts +++ b/packages/http-specs/specs/parameters/collection-format/mockapi.ts @@ -31,12 +31,6 @@ Scenarios.Parameters_CollectionFormat_Query_csv = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("colors", ["blue", "red", "green"], "csv"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -49,12 +43,6 @@ Scenarios.Parameters_CollectionFormat_Query_ssv = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("colors", ["blue", "red", "green"], "ssv"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -67,12 +55,6 @@ Scenarios.Parameters_CollectionFormat_Query_tsv = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("colors", ["blue", "red", "green"], "tsv"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -85,12 +67,6 @@ Scenarios.Parameters_CollectionFormat_Query_pipes = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("colors", ["blue", "red", "green"], "pipes"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -103,11 +79,5 @@ Scenarios.Parameters_CollectionFormat_Header_csv = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("colors", "blue,red,green"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/parameters/spread/main.tsp b/packages/http-specs/specs/parameters/spread/main.tsp index 5c2f6ff379..5d8d348844 100644 --- a/packages/http-specs/specs/parameters/spread/main.tsp +++ b/packages/http-specs/specs/parameters/spread/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for the spread operator.") @scenarioService("/parameters/spread") diff --git a/packages/http-specs/specs/parameters/spread/mockapi.ts b/packages/http-specs/specs/parameters/spread/mockapi.ts index cf675e67a3..171a52cd97 100644 --- a/packages/http-specs/specs/parameters/spread/mockapi.ts +++ b/packages/http-specs/specs/parameters/spread/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Parameters_Spread_Model_spreadAsRequestBody = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -31,10 +27,6 @@ Scenarios.Parameters_Spread_Model_spreadCompositeRequestOnlyWithBody = passOnSuc response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -49,10 +41,6 @@ Scenarios.Parameters_Spread_Model_spreadCompositeRequestWithoutBody = passOnSucc response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("test-header", "bar"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -70,11 +58,6 @@ Scenarios.Parameters_Spread_Model_spreadCompositeRequest = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("test-header", "bar"); - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -92,11 +75,6 @@ Scenarios.Parameters_Spread_Model_spreadCompositeRequestMix = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("test-header", "bar"); - req.expect.bodyEquals({ prop: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -111,10 +89,6 @@ Scenarios.Parameters_Spread_Alias_spreadAsRequestBody = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -132,11 +106,6 @@ Scenarios.Parameters_Spread_Alias_spreadAsRequestParameter = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-test-header", "bar"); - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -157,16 +126,6 @@ Scenarios.Parameters_Spread_Alias_spreadWithMultipleParameters = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-test-header", "bar"); - req.expect.bodyEquals({ - requiredString: "foo", - optionalInt: 1, - requiredIntList: [1, 2], - optionalStringList: ["foo", "bar"], - }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -184,11 +143,6 @@ Scenarios.Parameters_Spread_Alias_spreadParameterWithInnerModel = passOnSuccess( response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-test-header", "bar"); - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -207,10 +161,5 @@ Scenarios.Parameters_Spread_Alias_spreadParameterWithInnerAlias = passOnSuccess( response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-test-header", "bar"); - req.expect.bodyEquals({ name: "foo", age: 1 }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/payload/content-negotiation/main.tsp b/packages/http-specs/specs/payload/content-negotiation/main.tsp index 3a1055af95..7dc2ad3c3c 100644 --- a/packages/http-specs/specs/payload/content-negotiation/main.tsp +++ b/packages/http-specs/specs/payload/content-negotiation/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test describing optionality of the request body.") @scenarioService("/content-negotiation") diff --git a/packages/http-specs/specs/payload/json-merge-patch/main.tsp b/packages/http-specs/specs/payload/json-merge-patch/main.tsp index a4f502e387..00686cd844 100644 --- a/packages/http-specs/specs/payload/json-merge-patch/main.tsp +++ b/packages/http-specs/specs/payload/json-merge-patch/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for merge-patch+json content-type") @scenarioService("/json-merge-patch") diff --git a/packages/http-specs/specs/payload/json-merge-patch/mockapi.ts b/packages/http-specs/specs/payload/json-merge-patch/mockapi.ts index 79390e0473..6fa60ba349 100644 --- a/packages/http-specs/specs/payload/json-merge-patch/mockapi.ts +++ b/packages/http-specs/specs/payload/json-merge-patch/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -53,13 +53,6 @@ Scenarios.Payload_JsonMergePatch_createResource = passOnSuccess({ status: 200, body: json(expectedCreateBody), }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals(expectedCreateBody); - return { - status: 200, - body: json(expectedCreateBody), - }; - }, kind: "MockApiDefinition", }); @@ -80,27 +73,6 @@ Scenarios.Payload_JsonMergePatch_updateResource = passOnSuccess({ }, }), }, - handler: (req: MockRequest) => { - req.expect.deepEqual(req.body.description, expectedUpdateBody.description); - req.expect.deepEqual(req.body.map.key.description, expectedUpdateBody.map.key.description); - req.expect.deepEqual(req.body.map.key2, expectedUpdateBody.map.key2); - req.expect.deepEqual(req.body.array, expectedUpdateBody.array); - req.expect.deepEqual(req.body.intValue, expectedUpdateBody.intValue); - req.expect.deepEqual(req.body.floatValue, expectedUpdateBody.floatValue); - req.expect.deepEqual(req.body.innerModel, expectedUpdateBody.innerModel); - req.expect.deepEqual(req.body.intArray, expectedUpdateBody.intArray); - return { - status: 200, - body: json({ - name: "Madge", - map: { - key: { - name: "InnerMadge", - }, - }, - }), - }; - }, kind: "MockApiDefinition", }); @@ -121,26 +93,5 @@ Scenarios.Payload_JsonMergePatch_updateOptionalResource = passOnSuccess({ }, }), }, - handler: (req: MockRequest) => { - req.expect.deepEqual(req.body.description, expectedUpdateBody.description); - req.expect.deepEqual(req.body.map.key.description, expectedUpdateBody.map.key.description); - req.expect.deepEqual(req.body.map.key2, expectedUpdateBody.map.key2); - req.expect.deepEqual(req.body.array, expectedUpdateBody.array); - req.expect.deepEqual(req.body.intValue, expectedUpdateBody.intValue); - req.expect.deepEqual(req.body.floatValue, expectedUpdateBody.floatValue); - req.expect.deepEqual(req.body.innerModel, expectedUpdateBody.innerModel); - req.expect.deepEqual(req.body.intArray, expectedUpdateBody.intArray); - return { - status: 200, - body: json({ - name: "Madge", - map: { - key: { - name: "InnerMadge", - }, - }, - }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/payload/media-type/main.tsp b/packages/http-specs/specs/payload/media-type/main.tsp index 6567355f6e..3ced443cbb 100644 --- a/packages/http-specs/specs/payload/media-type/main.tsp +++ b/packages/http-specs/specs/payload/media-type/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Test the payload with different media types and different types of the payload itself. diff --git a/packages/http-specs/specs/payload/media-type/mockapi.ts b/packages/http-specs/specs/payload/media-type/mockapi.ts index 0e9d04174d..71775f9f7e 100644 --- a/packages/http-specs/specs/payload/media-type/mockapi.ts +++ b/packages/http-specs/specs/payload/media-type/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -14,11 +14,6 @@ Scenarios.Payload_MediaType_StringBody_sendAsText = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("content-type", "text/plain"); - req.expect.bodyEquals("{cat}"); - return { status: 200 }; - }, kind: "MockApiDefinition", }); @@ -34,13 +29,6 @@ Scenarios.Payload_MediaType_StringBody_getAsText = passOnSuccess({ status: 200, body: { rawContent: "{cat}", contentType: "text/plain" }, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("accept", "text/plain"); - return { - status: 200, - body: { rawContent: "{cat}", contentType: "text/plain" }, - }; - }, kind: "MockApiDefinition", }); @@ -56,11 +44,6 @@ Scenarios.Payload_MediaType_StringBody_sendAsJson = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("content-type", "application/json"); - req.expect.bodyEquals("foo"); - return { status: 200 }; - }, kind: "MockApiDefinition", }); @@ -76,13 +59,5 @@ Scenarios.Payload_MediaType_StringBody_getAsJson = passOnSuccess({ status: 200, body: json("foo"), }, - handler: (req: MockRequest) => { - req.expect.containsHeader("accept", "application/json"); - return { - status: 200, - body: json("foo"), - contentType: "application/json", - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/payload/multipart/main.tsp b/packages/http-specs/specs/payload/multipart/main.tsp index aff1c2f860..f94de86ae9 100644 --- a/packages/http-specs/specs/payload/multipart/main.tsp +++ b/packages/http-specs/specs/payload/multipart/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for multipart") @scenarioService("/multipart") diff --git a/packages/http-specs/specs/payload/xml/main.tsp b/packages/http-specs/specs/payload/xml/main.tsp index b445b29676..68bf621eb8 100644 --- a/packages/http-specs/specs/payload/xml/main.tsp +++ b/packages/http-specs/specs/payload/xml/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; import "@typespec/xml"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; using TypeSpec.Xml; @doc("Sends and receives bodies in XML format.") diff --git a/packages/http-specs/specs/payload/xml/mockapi.ts b/packages/http-specs/specs/payload/xml/mockapi.ts index 23d7369d82..d6273feec8 100644 --- a/packages/http-specs/specs/payload/xml/mockapi.ts +++ b/packages/http-specs/specs/payload/xml/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi, xml } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi, xml } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -133,12 +133,6 @@ function createServerTests(uri: string, data?: any) { status: 200, body: xml(data), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: xml(data), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -153,13 +147,6 @@ function createServerTests(uri: string, data?: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("content-type", "application/xml"); - req.expect.xmlBodyEquals(data); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/routes/main.tsp b/packages/http-specs/specs/routes/main.tsp index 679ca79b03..9364b1aa81 100644 --- a/packages/http-specs/specs/routes/main.tsp +++ b/packages/http-specs/specs/routes/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Define scenario in building the http route/uri diff --git a/packages/http-specs/specs/routes/mockapi.ts b/packages/http-specs/specs/routes/mockapi.ts index ec8eb6a7af..b1a8013c4e 100644 --- a/packages/http-specs/specs/routes/mockapi.ts +++ b/packages/http-specs/specs/routes/mockapi.ts @@ -4,34 +4,29 @@ export const Scenarios: Record = {}; function createTests(uri: string) { const url = new URL("http://example.com" + uri); - const searchParams = url.searchParams; - const params: Record = {}; - for (const [key, value] of searchParams) { - params[key] = value; + const queryMap = new Map(); + for (const [key, value] of url.searchParams.entries()) { + if (queryMap.has(key)) { + const existing = queryMap.get(key)!; + if (Array.isArray(existing)) { + existing.push(value); + } else { + queryMap.set(key, [existing, value]); + } + } else { + queryMap.set(key, value); + } } return passOnSuccess({ uri: url.pathname, method: "get", request: { - params, + params: Object.fromEntries(queryMap), }, response: { status: 204, }, handler: (req: MockRequest) => { - const queryMap = new Map(); - for (const [key, value] of url.searchParams.entries()) { - if (queryMap.has(key)) { - const existing = queryMap.get(key)!; - if (Array.isArray(existing)) { - existing.push(value); - } else { - queryMap.set(key, [existing, value]); - } - } else { - queryMap.set(key, value); - } - } for (const [key, value] of queryMap.entries()) { if (Array.isArray(value)) { req.expect.containsQueryParam(key, value, "multi"); @@ -155,7 +150,7 @@ Scenarios.Routes_QueryParameters_QueryExpansion_Explode_primitive = createTests( "/routes/query/query-expansion/explode/primitive?param=a", ); Scenarios.Routes_QueryParameters_QueryExpansion_Explode_array = createTests( - "/routes/query/query-expansion/explode/array?param=a,b", + "/routes/query/query-expansion/explode/array?param=a¶m=b", ); Scenarios.Routes_QueryParameters_QueryExpansion_Explode_record = createTests( "/routes/query/query-expansion/explode/record?a=1&b=2", @@ -173,7 +168,7 @@ Scenarios.Routes_QueryParameters_QueryContinuation_Explode_primitive = createTes "/routes/query/query-continuation/explode/primitive?fixed=true¶m=a", ); Scenarios.Routes_QueryParameters_QueryContinuation_Explode_array = createTests( - "/routes/query/query-continuation/explode/array?fixed=true¶m=a,b", + "/routes/query/query-continuation/explode/array?fixed=true¶m=a¶m=b", ); Scenarios.Routes_QueryParameters_QueryContinuation_Explode_record = createTests( "/routes/query/query-continuation/explode/record?fixed=true&a=1&b=2", diff --git a/packages/http-specs/specs/serialization/encoded-name/json/main.tsp b/packages/http-specs/specs/serialization/encoded-name/json/main.tsp index 8082612cd3..de32ec6a02 100644 --- a/packages/http-specs/specs/serialization/encoded-name/json/main.tsp +++ b/packages/http-specs/specs/serialization/encoded-name/json/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Projection") @scenarioService("/serialization/encoded-name/json") diff --git a/packages/http-specs/specs/serialization/encoded-name/json/mockapi.ts b/packages/http-specs/specs/serialization/encoded-name/json/mockapi.ts index b20c74b5f6..d4cc9bf62b 100644 --- a/packages/http-specs/specs/serialization/encoded-name/json/mockapi.ts +++ b/packages/http-specs/specs/serialization/encoded-name/json/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -7,12 +7,6 @@ Scenarios.Serialization_EncodedName_Json_Property_send = passOnSuccess({ method: "post", request: { body: { wireName: true } }, response: { status: 204 }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ wireName: true }); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -24,11 +18,5 @@ Scenarios.Serialization_EncodedName_Json_Property_get = passOnSuccess({ status: 200, body: json({ wireName: true }), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json({ wireName: true }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/server/endpoint/not-defined/main.tsp b/packages/http-specs/specs/server/endpoint/not-defined/main.tsp index eb53790012..04962911dc 100644 --- a/packages/http-specs/specs/server/endpoint/not-defined/main.tsp +++ b/packages/http-specs/specs/server/endpoint/not-defined/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Illustrates server doesn't define endpoint. Client should automatically add an endpoint to let user pass in. diff --git a/packages/http-specs/specs/server/endpoint/not-defined/mockapi.ts b/packages/http-specs/specs/server/endpoint/not-defined/mockapi.ts index 4d87c11583..c3a58c0882 100644 --- a/packages/http-specs/specs/server/endpoint/not-defined/mockapi.ts +++ b/packages/http-specs/specs/server/endpoint/not-defined/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -9,8 +9,5 @@ Scenarios.Server_Endpoint_NotDefined_valid = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - return { status: 200 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/server/path/multiple/main.tsp b/packages/http-specs/specs/server/path/multiple/main.tsp index d785fd5563..868684aeb3 100644 --- a/packages/http-specs/specs/server/path/multiple/main.tsp +++ b/packages/http-specs/specs/server/path/multiple/main.tsp @@ -1,9 +1,9 @@ import "@typespec/rest"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; using TypeSpec.Rest; diff --git a/packages/http-specs/specs/server/path/multiple/mockapi.ts b/packages/http-specs/specs/server/path/multiple/mockapi.ts index 6d2fa3bd1c..8c5fef06e1 100644 --- a/packages/http-specs/specs/server/path/multiple/mockapi.ts +++ b/packages/http-specs/specs/server/path/multiple/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -9,9 +9,6 @@ Scenarios.Server_Path_Multiple_noOperationParams = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -22,8 +19,5 @@ Scenarios.Server_Path_Multiple_withOperationPathParam = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - return { status: 204 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/server/path/single/main.tsp b/packages/http-specs/specs/server/path/single/main.tsp index 381b58d86a..fd8713c47a 100644 --- a/packages/http-specs/specs/server/path/single/main.tsp +++ b/packages/http-specs/specs/server/path/single/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates server with a single path parameter @server") @service diff --git a/packages/http-specs/specs/server/path/single/mockapi.ts b/packages/http-specs/specs/server/path/single/mockapi.ts index cd2f2615c2..93619e7357 100644 --- a/packages/http-specs/specs/server/path/single/mockapi.ts +++ b/packages/http-specs/specs/server/path/single/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -9,8 +9,5 @@ Scenarios.Server_Path_Single_myOp = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - return { status: 200 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/server/versions/not-versioned/main.tsp b/packages/http-specs/specs/server/versions/not-versioned/main.tsp index 749ee8934a..0f8bafb36c 100644 --- a/packages/http-specs/specs/server/versions/not-versioned/main.tsp +++ b/packages/http-specs/specs/server/versions/not-versioned/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Illustrates not-versioned server. diff --git a/packages/http-specs/specs/server/versions/not-versioned/mockapi.ts b/packages/http-specs/specs/server/versions/not-versioned/mockapi.ts index 9e4b511d60..e8737bd640 100644 --- a/packages/http-specs/specs/server/versions/not-versioned/mockapi.ts +++ b/packages/http-specs/specs/server/versions/not-versioned/mockapi.ts @@ -47,9 +47,5 @@ Scenarios.Server_Versions_NotVersioned_withQueryApiVersion = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("api-version", "v1.0"); - return { status: 200 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/server/versions/versioned/main.tsp b/packages/http-specs/specs/server/versions/versioned/main.tsp index 9c3744556f..4a98569d15 100644 --- a/packages/http-specs/specs/server/versions/versioned/main.tsp +++ b/packages/http-specs/specs/server/versions/versioned/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/server/versions/versioned/mockapi.ts b/packages/http-specs/specs/server/versions/versioned/mockapi.ts index f1e6e1b11a..7bae7e0a6b 100644 --- a/packages/http-specs/specs/server/versions/versioned/mockapi.ts +++ b/packages/http-specs/specs/server/versions/versioned/mockapi.ts @@ -39,10 +39,6 @@ function createAPIVersionTests(uri: string, requestData: any, serverData: string response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("api-version", serverData); - return { status: 200 }; - }, kind: "MockApiDefinition", }); } diff --git a/packages/http-specs/specs/special-headers/conditional-request/main.tsp b/packages/http-specs/specs/special-headers/conditional-request/main.tsp index 2dea074f85..3f6b6d8979 100644 --- a/packages/http-specs/specs/special-headers/conditional-request/main.tsp +++ b/packages/http-specs/specs/special-headers/conditional-request/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; import "@typespec/versioning"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; @doc("Illustrates conditional request headers") diff --git a/packages/http-specs/specs/special-headers/conditional-request/mockapi.ts b/packages/http-specs/specs/special-headers/conditional-request/mockapi.ts index 74dd4d40e6..ddad3105af 100644 --- a/packages/http-specs/specs/special-headers/conditional-request/mockapi.ts +++ b/packages/http-specs/specs/special-headers/conditional-request/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,12 +13,6 @@ Scenarios.SpecialHeaders_ConditionalRequest_postIfUnmodifiedSince = passOnSucces response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("if-unmodified-since", "Fri, 26 Aug 2022 14:38:00 GMT"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -33,12 +27,6 @@ Scenarios.SpecialHeaders_ConditionalRequest_headIfModifiedSince = passOnSuccess( response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("if-modified-since", "Fri, 26 Aug 2022 14:38:00 GMT"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -53,12 +41,6 @@ Scenarios.SpecialHeaders_ConditionalRequest_postIfMatch = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("if-match", '"valid"'); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -73,11 +55,5 @@ Scenarios.SpecialHeaders_ConditionalRequest_postIfNoneMatch = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("if-none-match", '"invalid"'); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/special-headers/repeatability/main.tsp b/packages/http-specs/specs/special-headers/repeatability/main.tsp index af5ed18094..e9bb0067ef 100644 --- a/packages/http-specs/specs/special-headers/repeatability/main.tsp +++ b/packages/http-specs/specs/special-headers/repeatability/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; import "@typespec/versioning"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; @doc("Illustrates OASIS repeatability headers") diff --git a/packages/http-specs/specs/special-words/dec.js b/packages/http-specs/specs/special-words/dec.js index 5486579b4e..b3188c2b22 100644 --- a/packages/http-specs/specs/special-words/dec.js +++ b/packages/http-specs/specs/special-words/dec.js @@ -1,7 +1,7 @@ // @ts-check import { $route } from "@typespec/http"; -import { $scenario, $scenarioDoc } from "@typespec/spec-lib"; +import { $scenario, $scenarioDoc } from "@typespec/spector"; /** * diff --git a/packages/http-specs/specs/special-words/main.tsp b/packages/http-specs/specs/special-words/main.tsp index 244deaebe8..d802c373fd 100644 --- a/packages/http-specs/specs/special-words/main.tsp +++ b/packages/http-specs/specs/special-words/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "./dec.js"; using Http; -using SpecLib; +using Spector; /** * Scenarios to verify that reserved words can be used in service and generators will handle it appropriately. diff --git a/packages/http-specs/specs/special-words/mockapi.ts b/packages/http-specs/specs/special-words/mockapi.ts index 9ade4e38b7..5dee179e4e 100644 --- a/packages/http-specs/specs/special-words/mockapi.ts +++ b/packages/http-specs/specs/special-words/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,12 +13,6 @@ Scenarios.SpecialWords_ModelProperties_sameAsModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ ["SameAsModel"]: "ok" }); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -34,12 +28,6 @@ function createModelsTests(uri: string) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "ok" }); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); } @@ -85,11 +73,6 @@ function createOperationsTests(uri: string) { response: { status: 204, }, - handler: (req: MockRequest) => { - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); } @@ -156,12 +139,6 @@ function createParametersTests(uri: string, data: any, paramName: string) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam(paramName, "ok"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); } diff --git a/packages/http-specs/specs/type/array/main.tsp b/packages/http-specs/specs/type/array/main.tsp index 4276140fcf..b91b2fe684 100644 --- a/packages/http-specs/specs/type/array/main.tsp +++ b/packages/http-specs/specs/type/array/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates various types of arrays.") @scenarioService("/type/array") diff --git a/packages/http-specs/specs/type/array/mockapi.ts b/packages/http-specs/specs/type/array/mockapi.ts index 469b8d3ea1..719a2e4d0f 100644 --- a/packages/http-specs/specs/type/array/mockapi.ts +++ b/packages/http-specs/specs/type/array/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -12,12 +12,6 @@ function createServerTests(uri: string, data: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(data), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -29,12 +23,6 @@ function createServerTests(uri: string, data: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals(data); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/dictionary/main.tsp b/packages/http-specs/specs/type/dictionary/main.tsp index ff97282ea0..bf8f558652 100644 --- a/packages/http-specs/specs/type/dictionary/main.tsp +++ b/packages/http-specs/specs/type/dictionary/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates various of dictionaries.") @scenarioService("/type/dictionary") diff --git a/packages/http-specs/specs/type/dictionary/mockapi.ts b/packages/http-specs/specs/type/dictionary/mockapi.ts index a7b28a2587..159ed922e7 100644 --- a/packages/http-specs/specs/type/dictionary/mockapi.ts +++ b/packages/http-specs/specs/type/dictionary/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -12,12 +12,6 @@ function createServerTests(uri: string, data: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(data), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -29,12 +23,6 @@ function createServerTests(uri: string, data: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals(data); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/enum/extensible/main.tsp b/packages/http-specs/specs/type/enum/extensible/main.tsp index ba7899108b..405dd0c9be 100644 --- a/packages/http-specs/specs/type/enum/extensible/main.tsp +++ b/packages/http-specs/specs/type/enum/extensible/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/type/enum/extensible") namespace Type.Enum.Extensible; diff --git a/packages/http-specs/specs/type/enum/extensible/mockapi.ts b/packages/http-specs/specs/type/enum/extensible/mockapi.ts index 71bd51f067..9c51d6570b 100644 --- a/packages/http-specs/specs/type/enum/extensible/mockapi.ts +++ b/packages/http-specs/specs/type/enum/extensible/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -12,9 +12,6 @@ function createMockServerTests(uri: string, data: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(data) }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -29,10 +26,6 @@ function createMockServerTests(uri: string, data: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(data); - return { status: 204 }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/enum/fixed/main.tsp b/packages/http-specs/specs/type/enum/fixed/main.tsp index 846f0c3498..327a5afcd1 100644 --- a/packages/http-specs/specs/type/enum/fixed/main.tsp +++ b/packages/http-specs/specs/type/enum/fixed/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/type/enum/fixed") namespace Type.Enum.Fixed; diff --git a/packages/http-specs/specs/type/enum/fixed/mockapi.ts b/packages/http-specs/specs/type/enum/fixed/mockapi.ts index e06624e958..41f0343175 100644 --- a/packages/http-specs/specs/type/enum/fixed/mockapi.ts +++ b/packages/http-specs/specs/type/enum/fixed/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -10,9 +10,6 @@ Scenarios.Type_Enum_Fixed_String_getKnownValue = passOnSuccess({ status: 200, body: json("Monday"), }, - handler: (req: MockRequest) => { - return { status: 200, body: json("Monday") }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Enum_Fixed_String_putKnownValue = passOnSuccess({ @@ -27,10 +24,6 @@ Scenarios.Type_Enum_Fixed_String_putKnownValue = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals("Monday"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -47,9 +40,5 @@ Scenarios.Type_Enum_Fixed_String_putUnknownValue = passOnSuccess({ response: { status: 500, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals("Weekend"); - return { status: 500 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/empty/main.tsp b/packages/http-specs/specs/type/model/empty/main.tsp index b8f3b917bd..bbfb6194c6 100644 --- a/packages/http-specs/specs/type/model/empty/main.tsp +++ b/packages/http-specs/specs/type/model/empty/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates usage of empty model used in operation's parameters and responses.") @scenarioService("/type/model/empty") diff --git a/packages/http-specs/specs/type/model/empty/mockapi.ts b/packages/http-specs/specs/type/model/empty/mockapi.ts index cb5858ea63..a4ab1731ef 100644 --- a/packages/http-specs/specs/type/model/empty/mockapi.ts +++ b/packages/http-specs/specs/type/model/empty/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Type_Model_Empty_putEmpty = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(body); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Empty_getEmpty = passOnSuccess({ @@ -27,9 +23,6 @@ Scenarios.Type_Model_Empty_getEmpty = passOnSuccess({ status: 200, body: json(body), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(body) }; - }, kind: "MockApiDefinition", }); @@ -43,9 +36,5 @@ Scenarios.Type_Model_Empty_postRoundTripEmpty = passOnSuccess({ status: 200, body: json(body), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(body); - return { status: 200, body: json(body) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/inheritance/enum-discriminator/main.tsp b/packages/http-specs/specs/type/model/inheritance/enum-discriminator/main.tsp index 870699717f..e7893d7310 100644 --- a/packages/http-specs/specs/type/model/inheritance/enum-discriminator/main.tsp +++ b/packages/http-specs/specs/type/model/inheritance/enum-discriminator/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates inheritance with enum discriminator.") @scenarioService("/type/model/inheritance/enum-discriminator") diff --git a/packages/http-specs/specs/type/model/inheritance/enum-discriminator/mockapi.ts b/packages/http-specs/specs/type/model/inheritance/enum-discriminator/mockapi.ts index b288187344..eb06680737 100644 --- a/packages/http-specs/specs/type/model/inheritance/enum-discriminator/mockapi.ts +++ b/packages/http-specs/specs/type/model/inheritance/enum-discriminator/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -19,9 +19,6 @@ function createGetServerTests(uri: string, data: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(data) }; - }, kind: "MockApiDefinition", }); } @@ -36,9 +33,6 @@ function createGetPutServerTests(uri: string, data: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(data) }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -50,10 +44,6 @@ function createGetPutServerTests(uri: string, data: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(data); - return { status: 204 }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/model/inheritance/nested-discriminator/main.tsp b/packages/http-specs/specs/type/model/inheritance/nested-discriminator/main.tsp index 3ef6b307ec..fdf750bf53 100644 --- a/packages/http-specs/specs/type/model/inheritance/nested-discriminator/main.tsp +++ b/packages/http-specs/specs/type/model/inheritance/nested-discriminator/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates multiple level inheritance with multiple discriminators.") @scenarioService("/type/model/inheritance/nested-discriminator") diff --git a/packages/http-specs/specs/type/model/inheritance/nested-discriminator/mockapi.ts b/packages/http-specs/specs/type/model/inheritance/nested-discriminator/mockapi.ts index a35af68f7d..9b657fec1a 100644 --- a/packages/http-specs/specs/type/model/inheritance/nested-discriminator/mockapi.ts +++ b/packages/http-specs/specs/type/model/inheritance/nested-discriminator/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -72,9 +72,6 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_getModel = passOnSuccess({ status: 200, body: json(validPolymorphicBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(validPolymorphicBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_NestedDiscriminator_putModel = passOnSuccess({ @@ -86,10 +83,6 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_putModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(validPolymorphicBody); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -101,9 +94,6 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_getRecursiveModel = passOnS status: 200, body: json(validRecursiveBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(validRecursiveBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_NestedDiscriminator_putRecursiveModel = passOnSuccess({ @@ -115,10 +105,6 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_putRecursiveModel = passOnS response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(validRecursiveBody); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -130,9 +116,6 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_getMissingDiscriminator = p status: 200, body: json({ age: 1 }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ age: 1 }) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_NestedDiscriminator_getWrongDiscriminator = passOnSuccess({ @@ -143,8 +126,5 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_getWrongDiscriminator = pas status: 200, body: json({ age: 1, kind: "wrongKind" }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ age: 1, kind: "wrongKind" }) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/inheritance/not-discriminated/main.tsp b/packages/http-specs/specs/type/model/inheritance/not-discriminated/main.tsp index e8e893703d..69c186445b 100644 --- a/packages/http-specs/specs/type/model/inheritance/not-discriminated/main.tsp +++ b/packages/http-specs/specs/type/model/inheritance/not-discriminated/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates not-discriminated inheritance model.") @scenarioService("/type/model/inheritance/not-discriminated") diff --git a/packages/http-specs/specs/type/model/inheritance/not-discriminated/mockapi.ts b/packages/http-specs/specs/type/model/inheritance/not-discriminated/mockapi.ts index 3e1bbdb511..f8e3a83188 100644 --- a/packages/http-specs/specs/type/model/inheritance/not-discriminated/mockapi.ts +++ b/packages/http-specs/specs/type/model/inheritance/not-discriminated/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Type_Model_Inheritance_NotDiscriminated_postValid = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(inheritanceValidBody); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_NotDiscriminated_getValid = passOnSuccess({ @@ -27,9 +23,6 @@ Scenarios.Type_Model_Inheritance_NotDiscriminated_getValid = passOnSuccess({ status: 200, body: json(inheritanceValidBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(inheritanceValidBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_NotDiscriminated_putValid = passOnSuccess({ @@ -42,8 +35,5 @@ Scenarios.Type_Model_Inheritance_NotDiscriminated_putValid = passOnSuccess({ status: 200, body: json(inheritanceValidBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(req.body) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/inheritance/recursive/main.tsp b/packages/http-specs/specs/type/model/inheritance/recursive/main.tsp index 0e813d9919..a5656b9491 100644 --- a/packages/http-specs/specs/type/model/inheritance/recursive/main.tsp +++ b/packages/http-specs/specs/type/model/inheritance/recursive/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates inheritance recursion") @scenarioService("/type/model/inheritance/recursive") diff --git a/packages/http-specs/specs/type/model/inheritance/recursive/mockapi.ts b/packages/http-specs/specs/type/model/inheritance/recursive/mockapi.ts index f5cac6e012..30db7228d2 100644 --- a/packages/http-specs/specs/type/model/inheritance/recursive/mockapi.ts +++ b/packages/http-specs/specs/type/model/inheritance/recursive/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -27,10 +27,6 @@ Scenarios.Type_Model_Inheritance_Recursive_put = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(body); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_Recursive_get = passOnSuccess({ @@ -41,8 +37,5 @@ Scenarios.Type_Model_Inheritance_Recursive_get = passOnSuccess({ status: 200, body: json(body), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(body) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp b/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp index 189c7d21da..0a396d731a 100644 --- a/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp +++ b/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates inheritance with single discriminator.") @scenarioService("/type/model/inheritance/single-discriminator") diff --git a/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts b/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts index 9d86e79c21..ba6958ac18 100644 --- a/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts +++ b/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -34,9 +34,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_getModel = passOnSuccess({ status: 200, body: json(validPolymorphicBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(validPolymorphicBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_SingleDiscriminator_putModel = passOnSuccess({ @@ -48,10 +45,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_putModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(validPolymorphicBody); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -63,9 +56,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_getRecursiveModel = passOnS status: 200, body: json(validRecursiveBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(validRecursiveBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_SingleDiscriminator_putRecursiveModel = passOnSuccess({ @@ -77,10 +67,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_putRecursiveModel = passOnS response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(validRecursiveBody); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -92,9 +78,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_getMissingDiscriminator = p status: 200, body: json({ wingspan: 1 }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ wingspan: 1 }) }; - }, kind: "MockApiDefinition", }); @@ -106,9 +89,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_getWrongDiscriminator = pas status: 200, body: json({ wingspan: 1, kind: "wrongKind" }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ wingspan: 1, kind: "wrongKind" }) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_SingleDiscriminator_getLegacyModel = passOnSuccess({ @@ -119,8 +99,5 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_getLegacyModel = passOnSucc status: 200, body: json({ size: 20, kind: "t-rex" }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ size: 20, kind: "t-rex" }) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/templated/main.tsp b/packages/http-specs/specs/type/model/templated/main.tsp index 3905fa4dd2..3d9bc1d92e 100644 --- a/packages/http-specs/specs/type/model/templated/main.tsp +++ b/packages/http-specs/specs/type/model/templated/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Illustrates the model templated cases. There is a base templated type and an instantiated type extending from it. diff --git a/packages/http-specs/specs/type/model/templated/mockapi.ts b/packages/http-specs/specs/type/model/templated/mockapi.ts index 474f76920b..cd69b9bfa8 100644 --- a/packages/http-specs/specs/type/model/templated/mockapi.ts +++ b/packages/http-specs/specs/type/model/templated/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -20,18 +20,6 @@ Scenarios.Type_Model_Templated_numericType = passOnSuccess({ value: 1234, }), }, - handler: (req: MockRequest) => { - const body = { - kind: "Int32Values", - values: [1234], - value: 1234, - }; - req.expect.bodyEquals(body); - return { - status: 200, - body: json(body), - }; - }, kind: "MockApiDefinition", }); @@ -53,18 +41,6 @@ Scenarios.Type_Model_Templated_float32Type = passOnSuccess({ value: 0.5, }), }, - handler: (req: MockRequest) => { - const body = { - kind: "Float32Values", - values: [0.5], - value: 0.5, - }; - req.expect.bodyEquals(body); - return { - status: 200, - body: json(body), - }; - }, kind: "MockApiDefinition", }); @@ -86,17 +62,5 @@ Scenarios.Type_Model_Templated_int32Type = passOnSuccess({ value: 1234, }), }, - handler: (req: MockRequest) => { - const body = { - kind: "Int32Values", - values: [1234], - value: 1234, - }; - req.expect.bodyEquals(body); - return { - status: 200, - body: json(body), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/usage/main.tsp b/packages/http-specs/specs/type/model/usage/main.tsp index 60fe11f9d3..5197007a0f 100644 --- a/packages/http-specs/specs/type/model/usage/main.tsp +++ b/packages/http-specs/specs/type/model/usage/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Those tests are meant to test different behavior of records if they are used as input, output or both. diff --git a/packages/http-specs/specs/type/model/usage/mockapi.ts b/packages/http-specs/specs/type/model/usage/mockapi.ts index caa1c77e5e..f705391f97 100644 --- a/packages/http-specs/specs/type/model/usage/mockapi.ts +++ b/packages/http-specs/specs/type/model/usage/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -15,10 +15,6 @@ Scenarios.Type_Model_Usage_input = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(body); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -30,9 +26,6 @@ Scenarios.Type_Model_Usage_output = passOnSuccess({ status: 200, body: json(body), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(body) }; - }, kind: "MockApiDefinition", }); @@ -48,9 +41,5 @@ Scenarios.Type_Model_Usage_inputAndOutput = passOnSuccess({ status: 200, body: json(body), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(body); - return { status: 200, body: json(body) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/visibility/main.tsp b/packages/http-specs/specs/type/model/visibility/main.tsp index 14098f633a..37bec3992e 100644 --- a/packages/http-specs/specs/type/model/visibility/main.tsp +++ b/packages/http-specs/specs/type/model/visibility/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates models with visibility properties.") @scenarioService("/type/model/visibility") diff --git a/packages/http-specs/specs/type/model/visibility/mockapi.ts b/packages/http-specs/specs/type/model/visibility/mockapi.ts index 74a4a45c17..22ac0dfe64 100644 --- a/packages/http-specs/specs/type/model/visibility/mockapi.ts +++ b/packages/http-specs/specs/type/model/visibility/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -30,10 +30,6 @@ Scenarios.Type_Model_Visibility_putReadOnlyModel = passOnSuccess({ status: 200, body: json(expectBody), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({}); - return { status: 200, body: json(expectBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_headModel = passOnSuccess({ @@ -45,10 +41,6 @@ Scenarios.Type_Model_Visibility_headModel = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["queryProp"])); - return { status: 200 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_getModel = passOnSuccess({ @@ -61,13 +53,6 @@ Scenarios.Type_Model_Visibility_getModel = passOnSuccess({ status: 200, body: json(genData(["readProp"])), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["queryProp"])); - return { - status: 200, - body: json(genData(["readProp"])), - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_putModel = passOnSuccess({ @@ -82,10 +67,6 @@ Scenarios.Type_Model_Visibility_putModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["createProp", "updateProp"])); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_patchModel = passOnSuccess({ @@ -99,10 +80,6 @@ Scenarios.Type_Model_Visibility_patchModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["updateProp"])); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_postModel = passOnSuccess({ @@ -116,10 +93,6 @@ Scenarios.Type_Model_Visibility_postModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["createProp"])); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_deleteModel = passOnSuccess({ @@ -131,9 +104,5 @@ Scenarios.Type_Model_Visibility_deleteModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["deleteProp"])); - return { status: 204 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/property/additional-properties/main.tsp b/packages/http-specs/specs/type/property/additional-properties/main.tsp index 3a4d59432c..10bc60616a 100644 --- a/packages/http-specs/specs/type/property/additional-properties/main.tsp +++ b/packages/http-specs/specs/type/property/additional-properties/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Tests for additional properties of models") @scenarioService("/type/property/additionalProperties") diff --git a/packages/http-specs/specs/type/property/additional-properties/mockapi.ts b/packages/http-specs/specs/type/property/additional-properties/mockapi.ts index 672b1692e0..f029442b99 100644 --- a/packages/http-specs/specs/type/property/additional-properties/mockapi.ts +++ b/packages/http-specs/specs/type/property/additional-properties/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -108,12 +108,6 @@ function createServerTests(url: string, value: any) { status: 200, body: json(value), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(value), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -125,13 +119,6 @@ function createServerTests(url: string, value: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - const expectedBody = JSON.parse(JSON.stringify(value)); - req.expect.coercedBodyEquals(expectedBody); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/property/nullable/main.tsp b/packages/http-specs/specs/type/property/nullable/main.tsp index 78ae48b8b3..aa9c5b0f29 100644 --- a/packages/http-specs/specs/type/property/nullable/main.tsp +++ b/packages/http-specs/specs/type/property/nullable/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates models with nullable properties.") @scenarioService("/type/property/nullable") diff --git a/packages/http-specs/specs/type/property/nullable/mockapi.ts b/packages/http-specs/specs/type/property/nullable/mockapi.ts index 625ed5d388..5c54b8608c 100644 --- a/packages/http-specs/specs/type/property/nullable/mockapi.ts +++ b/packages/http-specs/specs/type/property/nullable/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -12,12 +12,6 @@ function createServerTests(url: string, value: unknown, patchNullableProperty?: status: 200, body: json(value), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(value), - }; - }, kind: "MockApiDefinition", }), patch: passOnSuccess({ @@ -35,15 +29,6 @@ function createServerTests(url: string, value: unknown, patchNullableProperty?: response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ - requiredProperty: "foo", - nullableProperty: patchNullableProperty || null, - }); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/property/optionality/main.tsp b/packages/http-specs/specs/type/property/optionality/main.tsp index 83f884934b..7c7cd06000 100644 --- a/packages/http-specs/specs/type/property/optionality/main.tsp +++ b/packages/http-specs/specs/type/property/optionality/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates models with optional properties.") @scenarioService("/type/property/optional") diff --git a/packages/http-specs/specs/type/property/optionality/mockapi.ts b/packages/http-specs/specs/type/property/optionality/mockapi.ts index 874f1fb841..bb8234c1b8 100644 --- a/packages/http-specs/specs/type/property/optionality/mockapi.ts +++ b/packages/http-specs/specs/type/property/optionality/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -12,12 +12,6 @@ function createServerTests(url: string, value: unknown) { status: 200, body: json(value), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(value), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -29,12 +23,6 @@ function createServerTests(url: string, value: unknown) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(value); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/property/value-types/main.tsp b/packages/http-specs/specs/type/property/value-types/main.tsp index a77373f482..3f661a4baa 100644 --- a/packages/http-specs/specs/type/property/value-types/main.tsp +++ b/packages/http-specs/specs/type/property/value-types/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates various property types for models") @scenarioService("/type/property/value-types") diff --git a/packages/http-specs/specs/type/property/value-types/mockapi.ts b/packages/http-specs/specs/type/property/value-types/mockapi.ts index 7bc9bc68bd..45a9574223 100644 --- a/packages/http-specs/specs/type/property/value-types/mockapi.ts +++ b/packages/http-specs/specs/type/property/value-types/mockapi.ts @@ -2,14 +2,7 @@ import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spe export const Scenarios: Record = {}; -function createServerTests(url: string, data: unknown, convertedToFn?: (_: any) => any) { - let property; - if (convertedToFn) { - property = convertedToFn(data); - } else { - property = data; - } - +function createServerTests(url: string, data: unknown) { return { get: passOnSuccess({ uri: url, @@ -19,30 +12,17 @@ function createServerTests(url: string, data: unknown, convertedToFn?: (_: any) status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(data), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ uri: url, method: `put`, request: { - body: property, + body: data, }, response: { status: 204, }, - handler: (req: MockRequest) => { - const expectedBody = JSON.parse(JSON.stringify(property)); - req.expect.coercedBodyEquals(expectedBody); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; @@ -176,7 +156,30 @@ const Type_Property_ValueTypes_Never = createServerTests(`/type/property/value-t property: undefined, }); Scenarios.Type_Property_ValueTypes_Never_get = Type_Property_ValueTypes_Never.get; -Scenarios.Type_Property_ValueTypes_Never_put = Type_Property_ValueTypes_Never.put; +Scenarios.Type_Property_ValueTypes_Never_put = passOnSuccess({ + uri: `/type/property/value-types/never`, + method: `put`, + request: { + body: { + property: undefined, + }, + }, + response: { + status: 204, + }, + handler: (req: MockRequest) => { + const expectedBody = JSON.parse( + JSON.stringify({ + property: undefined, + }), + ); + req.expect.coercedBodyEquals(expectedBody); + return { + status: 204, + }; + }, + kind: "MockApiDefinition", +}); const Type_Property_ValueTypes_Unknown_String = createServerTests( `/type/property/value-types/unknown/string`, diff --git a/packages/http-specs/specs/type/scalar/main.tsp b/packages/http-specs/specs/type/scalar/main.tsp index 47cce3f1f6..b083d5c7b0 100644 --- a/packages/http-specs/specs/type/scalar/main.tsp +++ b/packages/http-specs/specs/type/scalar/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/type/scalar") namespace Type.Scalar; diff --git a/packages/http-specs/specs/type/scalar/mockapi.ts b/packages/http-specs/specs/type/scalar/mockapi.ts index 77ac2df9b2..97c5e5eeeb 100644 --- a/packages/http-specs/specs/type/scalar/mockapi.ts +++ b/packages/http-specs/specs/type/scalar/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -10,9 +10,6 @@ Scenarios.Type_Scalar_String_get = passOnSuccess({ status: 200, body: json("test"), }, - handler: (req: MockRequest) => { - return { status: 200, body: json("test") }; - }, kind: "MockApiDefinition", }); @@ -28,10 +25,6 @@ Scenarios.Type_Scalar_String_put = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals("test"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -43,9 +36,6 @@ Scenarios.Type_Scalar_Boolean_get = passOnSuccess({ status: 200, body: json(true), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(true) }; - }, kind: "MockApiDefinition", }); @@ -61,10 +51,6 @@ Scenarios.Type_Scalar_Boolean_put = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(true); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -76,9 +62,6 @@ Scenarios.Type_Scalar_Unknown_get = passOnSuccess({ status: 200, body: json("test"), }, - handler: (req: MockRequest) => { - return { status: 200, body: json("test") }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Unknown_put = passOnSuccess({ @@ -93,10 +76,6 @@ Scenarios.Type_Scalar_Unknown_put = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals("test"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -108,12 +87,6 @@ Scenarios.Type_Scalar_DecimalType_responseBody = passOnSuccess({ status: 200, body: json(0.33333), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(0.33333), - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Decimal128Type_responseBody = passOnSuccess({ @@ -124,12 +97,6 @@ Scenarios.Type_Scalar_Decimal128Type_responseBody = passOnSuccess({ status: 200, body: json(0.33333), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(0.33333), - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_DecimalType_requestBody = passOnSuccess({ @@ -144,12 +111,6 @@ Scenarios.Type_Scalar_DecimalType_requestBody = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(0.33333); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Decimal128Type_requestBody = passOnSuccess({ @@ -164,46 +125,28 @@ Scenarios.Type_Scalar_Decimal128Type_requestBody = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(0.33333); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_DecimalType_requestParameter = passOnSuccess({ uri: "/type/scalar/decimal/request_parameter", method: `get`, request: { - params: { value: 0.33333 }, + params: { value: "0.33333" }, }, response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("value", "0.33333"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Decimal128Type_requestParameter = passOnSuccess({ uri: "/type/scalar/decimal128/request_parameter", method: `get`, request: { - params: { value: 0.33333 }, + params: { value: "0.33333" }, }, response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("value", "0.33333"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_DecimalVerify_prepareVerify = passOnSuccess({ @@ -214,12 +157,6 @@ Scenarios.Type_Scalar_DecimalVerify_prepareVerify = passOnSuccess({ status: 200, body: json([0.1, 0.1, 0.1]), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json([0.1, 0.1, 0.1]), - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Decimal128Verify_prepareVerify = passOnSuccess({ @@ -230,12 +167,6 @@ Scenarios.Type_Scalar_Decimal128Verify_prepareVerify = passOnSuccess({ status: 200, body: json([0.1, 0.1, 0.1]), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json([0.1, 0.1, 0.1]), - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_DecimalVerify_verify = passOnSuccess({ @@ -250,12 +181,6 @@ Scenarios.Type_Scalar_DecimalVerify_verify = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(0.3); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Decimal128Verify_verify = passOnSuccess({ @@ -270,11 +195,5 @@ Scenarios.Type_Scalar_Decimal128Verify_verify = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(0.3); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/union/main.tsp b/packages/http-specs/specs/type/union/main.tsp index c31191d616..7df2d3387a 100644 --- a/packages/http-specs/specs/type/union/main.tsp +++ b/packages/http-specs/specs/type/union/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Describe scenarios for various combinations of unions. diff --git a/packages/http-specs/specs/type/union/mockapi.ts b/packages/http-specs/specs/type/union/mockapi.ts index da973f602e..16f5db19b1 100644 --- a/packages/http-specs/specs/type/union/mockapi.ts +++ b/packages/http-specs/specs/type/union/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -11,9 +11,6 @@ function createGetServerTests(url: string, value: unknown) { status: 200, body: json({ prop: value }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ prop: value }) }; - }, kind: "MockApiDefinition", }); } @@ -30,10 +27,6 @@ function createPostServerTests(url: string, value: unknown) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: value }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); } diff --git a/packages/http-specs/specs/versioning/added/main.tsp b/packages/http-specs/specs/versioning/added/main.tsp index 3bc5f7085b..a81594a47a 100644 --- a/packages/http-specs/specs/versioning/added/main.tsp +++ b/packages/http-specs/specs/versioning/added/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/versioning/added/mockapi.ts b/packages/http-specs/specs/versioning/added/mockapi.ts index 04bbf8aa67..b8cfec5253 100644 --- a/packages/http-specs/specs/versioning/added/mockapi.ts +++ b/packages/http-specs/specs/versioning/added/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -19,14 +19,6 @@ Scenarios.Versioning_Added_v1 = passOnSuccess({ status: 200, body: json({ prop: "foo", enumProp: "enumMemberV2", unionProp: 10 }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo", enumProp: "enumMemberV2", unionProp: 10 }); - req.expect.containsHeader("header-v2", "bar"); - return { - status: 200, - body: json({ prop: "foo", enumProp: "enumMemberV2", unionProp: 10 }), - }; - }, kind: "MockApiDefinition", }); @@ -44,13 +36,6 @@ Scenarios.Versioning_Added_v2 = passOnSuccess({ status: 200, body: json({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }); - return { - status: 200, - body: json({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }), - }; - }, kind: "MockApiDefinition", }); @@ -68,12 +53,5 @@ Scenarios.Versioning_Added_InterfaceV2 = passOnSuccess({ status: 200, body: json({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }); - return { - status: 200, - body: json({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/versioning/madeOptional/main.tsp b/packages/http-specs/specs/versioning/madeOptional/main.tsp index 98f052c66f..df1d25c018 100644 --- a/packages/http-specs/specs/versioning/madeOptional/main.tsp +++ b/packages/http-specs/specs/versioning/madeOptional/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/versioning/madeOptional/mockapi.ts b/packages/http-specs/specs/versioning/madeOptional/mockapi.ts index 145931af66..c13da829fa 100644 --- a/packages/http-specs/specs/versioning/madeOptional/mockapi.ts +++ b/packages/http-specs/specs/versioning/madeOptional/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -14,12 +14,5 @@ Scenarios.Versioning_MadeOptional_test = passOnSuccess({ status: 200, body: json({ prop: "foo" }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo" }); - return { - status: 200, - body: json({ prop: "foo" }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/versioning/removed/main.tsp b/packages/http-specs/specs/versioning/removed/main.tsp index 2ef7641aac..609df7c6d3 100644 --- a/packages/http-specs/specs/versioning/removed/main.tsp +++ b/packages/http-specs/specs/versioning/removed/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/versioning/removed/mockapi.ts b/packages/http-specs/specs/versioning/removed/mockapi.ts index 284abb8be3..7f4e2534cf 100644 --- a/packages/http-specs/specs/versioning/removed/mockapi.ts +++ b/packages/http-specs/specs/versioning/removed/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -16,12 +16,5 @@ Scenarios.Versioning_Removed_v2 = passOnSuccess({ status: 200, body: json({ prop: "foo", enumProp: "enumMemberV2", unionProp: "bar" }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo", enumProp: "enumMemberV2", unionProp: "bar" }); - return { - status: 200, - body: json({ prop: "foo", enumProp: "enumMemberV2", unionProp: "bar" }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/versioning/renamedFrom/main.tsp b/packages/http-specs/specs/versioning/renamedFrom/main.tsp index 9252a83173..c6e6e81fe2 100644 --- a/packages/http-specs/specs/versioning/renamedFrom/main.tsp +++ b/packages/http-specs/specs/versioning/renamedFrom/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/versioning/renamedFrom/mockapi.ts b/packages/http-specs/specs/versioning/renamedFrom/mockapi.ts index aa938dc57f..5069a74a5a 100644 --- a/packages/http-specs/specs/versioning/renamedFrom/mockapi.ts +++ b/packages/http-specs/specs/versioning/renamedFrom/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -19,14 +19,6 @@ Scenarios.Versioning_RenamedFrom_newOp = passOnSuccess({ status: 200, body: json({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }); - req.expect.containsQueryParam("newQuery", "bar"); - return { - status: 200, - body: json({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }), - }; - }, kind: "MockApiDefinition", }); @@ -44,12 +36,5 @@ Scenarios.Versioning_RenamedFrom_NewInterface = passOnSuccess({ status: 200, body: json({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }); - return { - status: 200, - body: json({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/versioning/returnTypeChangedFrom/main.tsp b/packages/http-specs/specs/versioning/returnTypeChangedFrom/main.tsp index c809b2ebf9..64944a191f 100644 --- a/packages/http-specs/specs/versioning/returnTypeChangedFrom/main.tsp +++ b/packages/http-specs/specs/versioning/returnTypeChangedFrom/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/versioning/returnTypeChangedFrom/mockapi.ts b/packages/http-specs/specs/versioning/returnTypeChangedFrom/mockapi.ts index 2fff5bcec0..a34afd223b 100644 --- a/packages/http-specs/specs/versioning/returnTypeChangedFrom/mockapi.ts +++ b/packages/http-specs/specs/versioning/returnTypeChangedFrom/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -15,12 +15,5 @@ Scenarios.Versioning_ReturnTypeChangedFrom_test = passOnSuccess({ status: 200, body: json("test"), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals("test"); - return { - status: 200, - body: json("test"), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/versioning/typeChangedFrom/main.tsp b/packages/http-specs/specs/versioning/typeChangedFrom/main.tsp index f62b45d413..acc8bd99e5 100644 --- a/packages/http-specs/specs/versioning/typeChangedFrom/main.tsp +++ b/packages/http-specs/specs/versioning/typeChangedFrom/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/versioning/typeChangedFrom/mockapi.ts b/packages/http-specs/specs/versioning/typeChangedFrom/mockapi.ts index 35425324f5..9e3405f2cb 100644 --- a/packages/http-specs/specs/versioning/typeChangedFrom/mockapi.ts +++ b/packages/http-specs/specs/versioning/typeChangedFrom/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -18,13 +18,5 @@ Scenarios.Versioning_TypeChangedFrom_test = passOnSuccess({ status: 200, body: json({ prop: "foo", changedProp: "bar" }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo", changedProp: "bar" }); - req.expect.containsQueryParam("param", "baz"); - return { - status: 200, - body: json({ prop: "foo", changedProp: "bar" }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/tsconfig.build.json b/packages/http-specs/tsconfig.build.json index 1b4589b0cc..39fcac3378 100644 --- a/packages/http-specs/tsconfig.build.json +++ b/packages/http-specs/tsconfig.build.json @@ -2,7 +2,6 @@ "extends": "./tsconfig.json", "references": [ { "path": "../spec-api/tsconfig.build.json" }, - { "path": "../spec-lib/tsconfig.build.json" }, { "path": "../spector/tsconfig.build.json" } ], "exclude": ["**/*.test.*", "test/**/*"] diff --git a/packages/openapi3/src/cli/actions/convert/interfaces.ts b/packages/openapi3/src/cli/actions/convert/interfaces.ts index eeb04eb2c3..c4e6897cea 100644 --- a/packages/openapi3/src/cli/actions/convert/interfaces.ts +++ b/packages/openapi3/src/cli/actions/convert/interfaces.ts @@ -112,6 +112,7 @@ export interface TypeSpecOperation extends TypeSpecDeclaration { export interface TypeSpecOperationParameter { name: string; + in: string; doc?: string; decorators: TypeSpecDecorator[]; isOptional: boolean; diff --git a/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts b/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts index a6480c1054..e616aef113 100644 --- a/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts +++ b/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts @@ -29,6 +29,7 @@ export function transformPaths( const operations: TypeSpecOperation[] = []; for (const route of Object.keys(paths)) { + const routeParameters = paths[route].parameters?.map(transformOperationParameter) ?? []; const path = paths[route]; for (const verb of supportedHttpMethods) { const operation = path[verb]; @@ -48,7 +49,7 @@ export function transformPaths( { name: "route", args: [route] }, { name: verb, args: [] }, ], - parameters, + parameters: dedupeParameters([...routeParameters, ...parameters]), doc: operation.description, operationId: operation.operationId, requestBodies: transformRequestBodies(operation.requestBody), @@ -61,6 +62,30 @@ export function transformPaths( return operations; } +function dedupeParameters( + parameters: Refable[], +): Refable[] { + const seen = new Set(); + const dedupeList: Refable[] = []; + + // iterate in reverse since more specific-scoped parameters are added last + for (let i = parameters.length - 1; i >= 0; i--) { + // ignore resolving the $ref for now, unlikely to be able to resolve + // issues without user intervention if a duplicate is present except in + // very simple cases. + const param = parameters[i]; + + const identifier = "$ref" in param ? param.$ref : `${param.in}.${param.name}`; + + if (seen.has(identifier)) continue; + seen.add(identifier); + + dedupeList.unshift(param); + } + + return dedupeList; +} + function transformOperationParameter( parameter: Refable, ): Refable { @@ -70,6 +95,7 @@ function transformOperationParameter( return { name: printIdentifier(parameter.name), + in: parameter.in, doc: parameter.description, decorators: getParameterDecorators(parameter), isOptional: !parameter.required, diff --git a/packages/openapi3/src/schema-emitter.ts b/packages/openapi3/src/schema-emitter.ts index 71c34c3fe9..90446bf0df 100644 --- a/packages/openapi3/src/schema-emitter.ts +++ b/packages/openapi3/src/schema-emitter.ts @@ -295,6 +295,13 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter< } } + const discriminator = getDiscriminator(this.emitter.getProgram(), model); + if (discriminator) { + if (!requiredProps.includes(discriminator.propertyName)) { + requiredProps.push(discriminator.propertyName); + } + } + return requiredProps.length > 0 ? requiredProps : undefined; } diff --git a/packages/openapi3/test/discriminator.test.ts b/packages/openapi3/test/discriminator.test.ts index 0d2bb8f4aa..88192cd26f 100644 --- a/packages/openapi3/test/discriminator.test.ts +++ b/packages/openapi3/test/discriminator.test.ts @@ -103,7 +103,7 @@ describe("openapi3: polymorphic model inheritance with discriminator", () => { name: { type: "string" }, weight: { type: "number", format: "float" }, }, - required: ["name"], + required: ["name", "kind"], discriminator: { propertyName: "kind", mapping: { @@ -154,7 +154,7 @@ describe("openapi3: polymorphic model inheritance with discriminator", () => { }, weight: { type: "number", format: "float" }, }, - required: ["name"], + required: ["name", "kind"], discriminator: { propertyName: "kind", mapping: { @@ -215,7 +215,7 @@ describe("openapi3: polymorphic model inheritance with discriminator", () => { name: { type: "string" }, weight: { type: "number", format: "float" }, }, - required: ["name"], + required: ["name", "kind"], discriminator: { propertyName: "kind", mapping: { @@ -234,7 +234,7 @@ describe("openapi3: polymorphic model inheritance with discriminator", () => { }, bark: { type: "string" }, }, - required: ["kind", "bark"], + required: ["kind", "bark", "breed"], allOf: [{ $ref: "#/components/schemas/Pet" }], discriminator: { propertyName: "breed", @@ -344,4 +344,18 @@ describe("openapi3: polymorphic model inheritance with discriminator", () => { }, ]); }); + + it("discriminator always needs to be marked as required", async () => { + const openApi = await openApiFor(` + @discriminator("kind") + model Animal { + id: string; + kind?: string; + }`); + + deepStrictEqual(openApi.components.schemas.Animal.required, ["id", "kind"]); + deepStrictEqual(openApi.components.schemas.Animal.discriminator, { + propertyName: "kind", + }); + }); }); diff --git a/packages/openapi3/test/tsp-openapi3/paths.test.ts b/packages/openapi3/test/tsp-openapi3/paths.test.ts new file mode 100644 index 0000000000..667d0ce2a9 --- /dev/null +++ b/packages/openapi3/test/tsp-openapi3/paths.test.ts @@ -0,0 +1,269 @@ +import assert from "assert"; +import { expect, it } from "vitest"; +import { OpenAPI3Response } from "../../src/types.js"; +import { expectDecorators } from "./utils/expect.js"; +import { tspForOpenAPI3 } from "./utils/tsp-for-openapi3.js"; + +const response: OpenAPI3Response = { + description: "test response", + content: { + "application/json": { + schema: { + type: "object", + properties: { + message: { type: "string" }, + }, + }, + }, + }, +}; + +it("generates operations with no params", async () => { + const serviceNamespace = await tspForOpenAPI3({ + paths: { + "/": { + get: { + operationId: "rootGet", + parameters: [], + responses: { + "200": response, + }, + }, + }, + }, + }); + + const operations = serviceNamespace.operations; + + expect(operations.size).toBe(1); + + /* @route("/") @get op rootGet(): rootGet200ApplicationJsonResponse; */ + const rootGet = operations.get("rootGet"); + assert(rootGet, "rootGet operation not found"); + + /* @get @route("/") */ + expectDecorators(rootGet.decorators, [{ name: "get" }, { name: "route", args: ["/"] }]); + // verify no operation parameters + expect(rootGet.parameters.properties.size).toBe(0); + assert(rootGet.returnType.kind === "Model", "Expected model return type"); + expect(rootGet.returnType.name).toBe("rootGet200ApplicationJsonResponse"); +}); + +it("generates operations without common params", async () => { + const serviceNamespace = await tspForOpenAPI3({ + paths: { + "/{id}": { + get: { + operationId: "idGet", + parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], + responses: { + "200": response, + }, + }, + }, + }, + }); + + const operations = serviceNamespace.operations; + + expect(operations.size).toBe(1); + + /* @route("/{id}") @get op idGet(@path id: string): idGet200ApplicationJsonResponse; */ + const idGet = operations.get("idGet"); + assert(idGet, "idGet operation not found"); + + /* @get @route("/{id}") */ + expectDecorators(idGet.decorators, [{ name: "get" }, { name: "route", args: ["/{id}"] }]); + + expect(idGet.parameters.properties.size).toBe(1); + const idParam = idGet.parameters.properties.get("id")!; + expect(idParam).toMatchObject({ + optional: false, + type: { kind: "Scalar", name: "string" }, + }); + expectDecorators(idParam.decorators, [{ name: "path" }]); + + assert(idGet.returnType.kind === "Model", "Expected model return type"); + expect(idGet.returnType.name).toBe("idGet200ApplicationJsonResponse"); +}); + +it("generates operations with common params", async () => { + const serviceNamespace = await tspForOpenAPI3({ + paths: { + "/{id}": { + parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], + get: { + operationId: "idGet", + parameters: [], + responses: { + "200": response, + }, + }, + }, + }, + }); + + const operations = serviceNamespace.operations; + + expect(operations.size).toBe(1); + + /* @route("/{id}") @get op idGet(@path id: string): idGet200ApplicationJsonResponse; */ + const idGet = operations.get("idGet"); + assert(idGet, "idGet operation not found"); + + /* @get @route("/{id}") */ + expectDecorators(idGet.decorators, [{ name: "get" }, { name: "route", args: ["/{id}"] }]); + + expect(idGet.parameters.properties.size).toBe(1); + const idParam = idGet.parameters.properties.get("id")!; + expect(idParam).toMatchObject({ + optional: false, + type: { kind: "Scalar", name: "string" }, + }); + expectDecorators(idParam.decorators, [{ name: "path" }]); + + assert(idGet.returnType.kind === "Model", "Expected model return type"); + expect(idGet.returnType.name).toBe("idGet200ApplicationJsonResponse"); +}); + +it("generates operations with common and specific params", async () => { + const serviceNamespace = await tspForOpenAPI3({ + paths: { + "/{id}": { + parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], + get: { + operationId: "idGet", + parameters: [{ name: "foo", in: "query", schema: { type: "string" } }], + responses: { + "200": response, + }, + }, + }, + }, + }); + + const operations = serviceNamespace.operations; + + expect(operations.size).toBe(1); + + /* @route("/{id}") @get op idGet(@path id: string, @query foo?: string): idGet200ApplicationJsonResponse; */ + const idGet = operations.get("idGet"); + assert(idGet, "idGet operation not found"); + + /* @get @route("/{id}") */ + expectDecorators(idGet.decorators, [{ name: "get" }, { name: "route", args: ["/{id}"] }]); + + /* (@path id: string, @query foo?: string) */ + expect(idGet.parameters.properties.size).toBe(2); + const idParam = idGet.parameters.properties.get("id")!; + expect(idParam).toMatchObject({ + optional: false, + type: { kind: "Scalar", name: "string" }, + }); + expectDecorators(idParam.decorators, { name: "path" }); + + const fooParam = idGet.parameters.properties.get("foo")!; + expect(fooParam).toMatchObject({ + optional: true, + type: { kind: "Scalar", name: "string" }, + }); + expectDecorators(fooParam.decorators, { name: "query" }); + + assert(idGet.returnType.kind === "Model", "Expected model return type"); + expect(idGet.returnType.name).toBe("idGet200ApplicationJsonResponse"); +}); + +it("supports overriding common params with operation params", async () => { + const serviceNamespace = await tspForOpenAPI3({ + paths: { + "/{id}": { + parameters: [ + { name: "id", in: "path", required: true, schema: { type: "string" } }, + { name: "x-header", in: "header", required: false, schema: { type: "string" } }, + ], + get: { + operationId: "idGet", + parameters: [ + { name: "foo", in: "query", schema: { type: "string" } }, + { name: "x-header", in: "header", required: true, schema: { type: "string" } }, + ], + responses: { + "200": response, + }, + }, + put: { + operationId: "idPut", + parameters: [], + responses: { + "200": response, + }, + }, + }, + }, + }); + + const operations = serviceNamespace.operations; + + expect(operations.size).toBe(2); + + // `idGet` overrides the common `x-header` parameter with it's own, making it required + /* @route("/{id}") @get op idGet(@path id: string, @query foo?: string, @header `x-header`: string): idGet200ApplicationJsonResponse; */ + const idGet = operations.get("idGet"); + assert(idGet, "idGet operation not found"); + + /* @get @route("/{id}") */ + expectDecorators(idGet.decorators, [{ name: "get" }, { name: "route", args: ["/{id}"] }]); + + /* (@path id: string, @query foo?: string, @header `x-header`: string) */ + expect(idGet.parameters.properties.size).toBe(3); + const idParam = idGet.parameters.properties.get("id")!; + expect(idParam).toMatchObject({ + optional: false, + type: { kind: "Scalar", name: "string" }, + }); + expectDecorators(idParam.decorators, { name: "path" }); + + const fooParam = idGet.parameters.properties.get("foo")!; + expect(fooParam).toMatchObject({ + optional: true, + type: { kind: "Scalar", name: "string" }, + }); + expectDecorators(fooParam.decorators, { name: "query" }); + + const xHeaderParam = idGet.parameters.properties.get("x-header")!; + expect(xHeaderParam).toMatchObject({ + optional: false, + type: { kind: "Scalar", name: "string" }, + }); + expectDecorators(xHeaderParam.decorators, { name: "header" }); + + assert(idGet.returnType.kind === "Model", "Expected model return type"); + expect(idGet.returnType.name).toBe("idGet200ApplicationJsonResponse"); + + // `idPut` uses the common `x-header` parameter, which is marked optional + /* @route("/{id}") @put op idPut(@path id: string, @header `x-header`: string): idPut200ApplicationJsonResponse; */ + const idPut = operations.get("idPut"); + assert(idPut, "idPut operation not found"); + + /* @put @route("/{id}") */ + expectDecorators(idPut.decorators, [{ name: "put" }, { name: "route", args: ["/{id}"] }]); + + /* (@path id: string, @header `x-header`?: string) */ + expect(idPut.parameters.properties.size).toBe(2); + const idPutParam = idPut.parameters.properties.get("id")!; + expect(idPutParam).toMatchObject({ + optional: false, + type: { kind: "Scalar", name: "string" }, + }); + expectDecorators(idPutParam.decorators, [{ name: "path" }]); + + const xHeaderSharedParam = idPut.parameters.properties.get("x-header")!; + expect(xHeaderSharedParam).toMatchObject({ + optional: true, + type: { kind: "Scalar", name: "string" }, + }); + expectDecorators(xHeaderSharedParam.decorators, { name: "header" }); + + assert(idPut.returnType.kind === "Model", "Expected model return type"); + expect(idPut.returnType.name).toBe("idPut200ApplicationJsonResponse"); +}); diff --git a/packages/openapi3/test/tsp-openapi3/utils/expect.ts b/packages/openapi3/test/tsp-openapi3/utils/expect.ts new file mode 100644 index 0000000000..55df73a60f --- /dev/null +++ b/packages/openapi3/test/tsp-openapi3/utils/expect.ts @@ -0,0 +1,57 @@ +import { DecoratorApplication, isType, Numeric } from "@typespec/compiler"; +import { expect } from "vitest"; + +export interface DecoratorMatch { + /** + * The name of the decorator without the "@" prefix. + */ + name: string; + + /** + * The arguments passed into the decorator. + */ + args?: any[]; +} + +export interface ExpectDecoratorsOptions { + strict?: boolean; +} + +export function expectDecorators( + decorators: DecoratorApplication[], + matches: DecoratorMatch | DecoratorMatch[], + options: ExpectDecoratorsOptions = { strict: true }, +) { + const expectations = Array.isArray(matches) ? matches : [matches]; + + if (options.strict) { + expect(decorators).toHaveLength(expectations.length); + } + + for (let i = 0; i < expectations.length; i++) { + const decorator = decorators[i]; + const expectation = expectations[i]; + + if (expectation.name) { + expect(decorator.definition?.name).toBe(`@${expectation.name}`); + } + + if (expectation.args) { + const args = expectation.args.map(transformDecoratorArg); + expect(decorator.args).toMatchObject(args); + } + } +} + +function transformDecoratorArg(arg: any) { + if (isType(arg)) return arg; + + if (typeof arg === "string") { + return { jsValue: arg }; + } + if (typeof arg === "number") { + return { jsValue: Numeric(`${arg}`) }; + } + + return arg; +} diff --git a/packages/openapi3/test/tsp-openapi3/utils/tsp-for-openapi3.ts b/packages/openapi3/test/tsp-openapi3/utils/tsp-for-openapi3.ts index 533fdfa1b9..fe8031062f 100644 --- a/packages/openapi3/test/tsp-openapi3/utils/tsp-for-openapi3.ts +++ b/packages/openapi3/test/tsp-openapi3/utils/tsp-for-openapi3.ts @@ -7,6 +7,7 @@ import { OpenAPI3TestLibrary } from "../../../src/testing/index.js"; import { OpenAPI3Document, OpenAPI3Parameter, + OpenAPI3PathItem, OpenAPI3Schema, Refable, } from "../../../src/types.js"; @@ -20,16 +21,17 @@ function wrapCodeInTest(code: string): string { export interface OpenAPI3Options { schemas?: Record>; parameters?: Record>; + paths?: Record; } -export async function tspForOpenAPI3({ parameters, schemas }: OpenAPI3Options) { +export async function tspForOpenAPI3({ parameters, paths, schemas }: OpenAPI3Options) { const openApi3Doc: OpenAPI3Document = { info: { title: "Test Service", version: "1.0.0", }, openapi: "3.0.0", - paths: {}, + paths: { ...paths }, components: { schemas: { ...(schemas as any), diff --git a/packages/playground/src/react/editor.tsx b/packages/playground/src/react/editor.tsx index 238a46ee9d..ce293c413a 100644 --- a/packages/playground/src/react/editor.tsx +++ b/packages/playground/src/react/editor.tsx @@ -32,10 +32,6 @@ export const Editor: FunctionComponent = ({ model, options, actions // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { - editor.setTheme(options.theme ?? "typespec"); - }, [options.theme]); - useEffect(() => { const disposables: IDisposable[] = []; for (const command of actions ?? []) { diff --git a/packages/playground/src/react/playground.tsx b/packages/playground/src/react/playground.tsx index 8356f815f0..d23879e9d8 100644 --- a/packages/playground/src/react/playground.tsx +++ b/packages/playground/src/react/playground.tsx @@ -110,6 +110,10 @@ export const Playground: FunctionComponent = (props) => { const { host, onSave } = props; const editorRef = useRef(undefined); + useEffect(() => { + editor.setTheme(props.editorOptions?.theme ?? "typespec"); + }, [props.editorOptions?.theme]); + const [selectedEmitter, onSelectedEmitterChange] = useControllableValue( props.emitter, props.defaultEmitter, diff --git a/packages/playground/src/services.ts b/packages/playground/src/services.ts index a903ca051b..b556785e4a 100644 --- a/packages/playground/src/services.ts +++ b/packages/playground/src/services.ts @@ -329,7 +329,6 @@ export async function registerMonacoLanguage(host: BrowserHost) { { token: "function", foreground: "#E06C75" }, ], }); - monaco.editor.setTheme("typespec"); monaco.languages.registerDocumentSemanticTokensProvider("typespec", { getLegend() { diff --git a/packages/samples/test/output/polymorphism/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/polymorphism/@typespec/openapi3/openapi.yaml index 6e9172b3a2..b8a536d4cc 100644 --- a/packages/samples/test/output/polymorphism/@typespec/openapi3/openapi.yaml +++ b/packages/samples/test/output/polymorphism/@typespec/openapi3/openapi.yaml @@ -50,6 +50,7 @@ components: type: object required: - name + - kind properties: name: type: string diff --git a/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v1.yaml b/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v1.yaml index 8befefdcfb..48b989550b 100644 --- a/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v1.yaml +++ b/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v1.yaml @@ -63,6 +63,7 @@ components: required: - name - favoriteToys + - type properties: name: type: string diff --git a/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v2.yaml b/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v2.yaml index 10b519e5f0..01e3552978 100644 --- a/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v2.yaml +++ b/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v2.yaml @@ -85,6 +85,7 @@ components: required: - name - favoriteToys + - type properties: name: type: string diff --git a/packages/spec-api/tsconfig.build.json b/packages/spec-api/tsconfig.build.json index 3498722d1d..4877190ccc 100644 --- a/packages/spec-api/tsconfig.build.json +++ b/packages/spec-api/tsconfig.build.json @@ -3,6 +3,6 @@ "compilerOptions": { "rootDir": "./src" }, - "references": [{ "path": "../spec-lib/tsconfig.build.json" }], + "references": [], "exclude": ["**/*.test.*", "test/**/*"] } diff --git a/packages/spec-coverage-sdk/tsconfig.build.json b/packages/spec-coverage-sdk/tsconfig.build.json index 3498722d1d..4877190ccc 100644 --- a/packages/spec-coverage-sdk/tsconfig.build.json +++ b/packages/spec-coverage-sdk/tsconfig.build.json @@ -3,6 +3,6 @@ "compilerOptions": { "rootDir": "./src" }, - "references": [{ "path": "../spec-lib/tsconfig.build.json" }], + "references": [], "exclude": ["**/*.test.*", "test/**/*"] } diff --git a/packages/spec-lib/CHANGELOG.md b/packages/spec-lib/CHANGELOG.md deleted file mode 100644 index 8754b76b9a..0000000000 --- a/packages/spec-lib/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# @typespec/spec-lib diff --git a/packages/spec-lib/package.json b/packages/spec-lib/package.json deleted file mode 100644 index c0da8a477a..0000000000 --- a/packages/spec-lib/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@typespec/spec-lib", - "version": "0.1.0", - "author": "Microsoft", - "description": "Spec Library providing decorator and validation for specs", - "homepage": "https://github.com/microsoft/typespec#readme", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/microsoft/typespec.git" - }, - "bugs": { - "url": "https://github.com/microsoft/typespec/issues" - }, - "type": "module", - "private": true, - "main": "dist/index.js", - "tspMain": "lib/lib.tsp", - "exports": { - ".": { - "typespec": "./lib/lib.tsp", - "default": "./dist/src/index.js" - } - }, - "engines": { - "node": ">=16.0.0" - }, - "scripts": { - "watch": "tsc -p . --watch", - "build": "npm run gen-extern-signature && tsc -p .", - "clean": "rimraf dist/ temp/", - "gen-extern-signature": "tspd --enable-experimental gen-extern-signature .", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "devDependencies": { - "@types/node": "~22.7.5", - "@typespec/tspd": "workspace:~", - "rimraf": "~6.0.1", - "typescript": "~5.6.3" - }, - "peerDependencies": { - "@typespec/compiler": "workspace:~", - "@typespec/http": "workspace:~", - "@typespec/rest": "workspace:~", - "@typespec/versioning": "workspace:~" - } -} diff --git a/packages/spec-lib/tsconfig.build.json b/packages/spec-lib/tsconfig.build.json deleted file mode 100644 index b4079ce75d..0000000000 --- a/packages/spec-lib/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["**/*.test.*", "test/**/*"] -} diff --git a/packages/spec-lib/tsconfig.config.json b/packages/spec-lib/tsconfig.config.json deleted file mode 100644 index 79fb341f39..0000000000 --- a/packages/spec-lib/tsconfig.config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": {} -} diff --git a/packages/spec-lib/tsconfig.json b/packages/spec-lib/tsconfig.json deleted file mode 100644 index 5745bb2c1c..0000000000 --- a/packages/spec-lib/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "references": [{ "path": "../compiler/tsconfig.json" }], - "compilerOptions": { - "outDir": "dist", - "rootDir": ".", - "tsBuildInfoFile": "temp/.tsbuildinfo" - }, - "include": ["src/**/*.ts", "generated-defs/**/*.ts", "test/**/*.ts"] -} diff --git a/packages/spector/cmd/cli.mjs b/packages/spector/cmd/cli.mjs index e2f595f637..73f921a55f 100755 --- a/packages/spector/cmd/cli.mjs +++ b/packages/spector/cmd/cli.mjs @@ -1,3 +1,3 @@ #!/usr/bin/env node -import "../dist/cli/cli.js"; +import "../dist/src/cli/cli.js"; diff --git a/packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts b/packages/spector/generated-defs/TypeSpec.Spector.ts similarity index 96% rename from packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts rename to packages/spector/generated-defs/TypeSpec.Spector.ts index 97dc5a2009..6003f5b786 100644 --- a/packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts +++ b/packages/spector/generated-defs/TypeSpec.Spector.ts @@ -39,7 +39,7 @@ export type ScenarioDocDecorator = ( formatArgs?: Model, ) => void; -export type TypeSpecSpecLibDecorators = { +export type TypeSpecSpectorDecorators = { scenarioService: ScenarioServiceDecorator; scenario: ScenarioDecorator; scenarioDoc: ScenarioDocDecorator; diff --git a/packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts-test.ts b/packages/spector/generated-defs/TypeSpec.Spector.ts-test.ts similarity index 57% rename from packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts-test.ts rename to packages/spector/generated-defs/TypeSpec.Spector.ts-test.ts index e9f03a6013..08dc02b6da 100644 --- a/packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts-test.ts +++ b/packages/spector/generated-defs/TypeSpec.Spector.ts-test.ts @@ -1,5 +1,5 @@ /** An error here would mean that the decorator is not exported or doesn't have the right name. */ -import { $decorators } from "@typespec/spec-lib"; -import type { TypeSpecSpecLibDecorators } from "./TypeSpec.SpecLib.js"; +import { $decorators } from "@typespec/spector"; +import type { TypeSpecSpectorDecorators } from "./TypeSpec.Spector.js"; /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ -const _: TypeSpecSpecLibDecorators = $decorators["TypeSpec.SpecLib"]; +const _: TypeSpecSpectorDecorators = $decorators["TypeSpec.Spector"]; diff --git a/packages/spec-lib/lib/lib.tsp b/packages/spector/lib/main.tsp similarity index 92% rename from packages/spec-lib/lib/lib.tsp rename to packages/spector/lib/main.tsp index 7c07d9df91..8749ec88fa 100644 --- a/packages/spec-lib/lib/lib.tsp +++ b/packages/spector/lib/main.tsp @@ -1,8 +1,8 @@ -import "../dist/src/tsp-index.js"; +import "../dist/src/lib/tsp-index.js"; using TypeSpec.Reflection; -namespace TypeSpec.SpecLib; +namespace TypeSpec.Spector; alias ScenarioServiceOptions = { /** diff --git a/packages/spector/package.json b/packages/spector/package.json index ab854ffb77..a4671eb970 100644 --- a/packages/spector/package.json +++ b/packages/spector/package.json @@ -2,7 +2,12 @@ "name": "@typespec/spector", "version": "0.1.0", "description": "Typespec Core Tool to validate, run mock api, collect coverage.", - "main": "dist/index.js", + "exports": { + ".": { + "import": "./dist/src/index.js", + "typespec": "./lib/main.tsp" + } + }, "private": true, "type": "module", "bin": { @@ -10,8 +15,9 @@ }, "scripts": { "watch": "tsc -p ./tsconfig.build.json --watch", - "build": "tsc -p ./tsconfig.build.json", + "build": "npm run gen-extern-signature && tsc -p tsconfig.build.json", "clean": "rimraf dist/ temp/", + "gen-extern-signature": "tspd --enable-experimental gen-extern-signature .", "test": "vitest run", "test:watch": "vitest -w", "test:ui": "vitest --ui" @@ -33,11 +39,11 @@ "@azure/identity": "~4.4.1", "@types/js-yaml": "^4.0.5", "@typespec/compiler": "workspace:~", + "@typespec/versioning": "workspace:~", "@typespec/http": "workspace:~", "@typespec/rest": "workspace:~", "@typespec/spec-api": "workspace:~", "@typespec/spec-coverage-sdk": "workspace:~", - "@typespec/spec-lib": "workspace:~", "ajv": "~8.17.1", "axios": "^1.7.5", "body-parser": "^1.20.3", @@ -67,6 +73,7 @@ "@types/node-fetch": "^2.6.3", "@types/xml2js": "^0.4.11", "@types/yargs": "~17.0.33", + "@typespec/tspd": "workspace:~", "rimraf": "~6.0.1", "typescript": "~5.6.3" } diff --git a/packages/spector/src/actions/generate-scenario-summary.ts b/packages/spector/src/actions/generate-scenario-summary.ts index da1891dc19..7817056f5d 100644 --- a/packages/spector/src/actions/generate-scenario-summary.ts +++ b/packages/spector/src/actions/generate-scenario-summary.ts @@ -1,7 +1,7 @@ -import { Scenario, ScenarioEndpoint } from "@typespec/spec-lib"; import { writeFile } from "fs/promises"; import pc from "picocolors"; import prettier from "prettier"; +import type { Scenario, ScenarioEndpoint } from "../lib/decorators.js"; import { logger } from "../logger.js"; import { loadScenarios } from "../scenarios-resolver.js"; diff --git a/packages/spector/src/app/app.ts b/packages/spector/src/app/app.ts index 49b9f03ec7..1ae839d6ae 100644 --- a/packages/spector/src/app/app.ts +++ b/packages/spector/src/app/app.ts @@ -1,4 +1,4 @@ -import { RequestExt, ScenarioMockApi } from "@typespec/spec-api"; +import { MockApiDefinition, MockRequest, RequestExt, ScenarioMockApi } from "@typespec/spec-api"; import { Response, Router } from "express"; import { getScenarioMetadata } from "../coverage/common.js"; import { CoverageTracker } from "../coverage/coverage-tracker.js"; @@ -56,7 +56,7 @@ export class MockApiApp { }); } else { if (!endpoint.handler) { - continue; + endpoint.handler = createHandler(endpoint); } this.router.route(endpoint.uri)[endpoint.method]((req: RequestExt, res: Response) => { processRequest( @@ -75,3 +75,42 @@ export class MockApiApp { } } } + +function createHandler(apiDefinition: MockApiDefinition) { + return (req: MockRequest) => { + // Validate body if present in the request + if (apiDefinition.request.body) { + if ( + apiDefinition.request.headers && + apiDefinition.request.headers["Content-Type"] === "application/xml" + ) { + req.expect.xmlBodyEquals(apiDefinition.request.body); + } else { + req.expect.coercedBodyEquals(apiDefinition.request.body); + } + } + + // Validate headers if present in the request + if (apiDefinition.request.headers) { + Object.entries(apiDefinition.request.headers).forEach(([key, value]) => { + if (key !== "Content-Type") { + req.expect.containsHeader(key, value as string); + } + }); + } + + // Validate query params if present in the request + if (apiDefinition.request.params) { + Object.entries(apiDefinition.request.params).forEach(([key, value]) => { + req.expect.containsQueryParam(key, value as string); + }); + } + + // Validations are done. Now return the response + return { + status: apiDefinition.response.status, + body: apiDefinition.response.body, + headers: apiDefinition.response.headers, + }; + }; +} diff --git a/packages/spector/src/coverage/scenario-manifest.ts b/packages/spector/src/coverage/scenario-manifest.ts index 91a8364c6a..681fa4490b 100644 --- a/packages/spector/src/coverage/scenario-manifest.ts +++ b/packages/spector/src/coverage/scenario-manifest.ts @@ -1,10 +1,10 @@ -import { Scenario } from "@typespec/spec-lib"; import { loadScenarios } from "../scenarios-resolver.js"; import { Diagnostic } from "../utils/diagnostic-reporter.js"; import { getCommit, getPackageJson } from "../utils/misc-utils.js"; import { ScenarioLocation, ScenarioManifest, GeneratorMode } from "@typespec/spec-coverage-sdk"; import { getSourceLocation, normalizePath } from "@typespec/compiler"; import { relative } from "path"; +import type { Scenario } from "../lib/decorators.js"; export async function computeScenarioManifest( scenariosPath: string, diff --git a/packages/spector/src/index.ts b/packages/spector/src/index.ts new file mode 100644 index 0000000000..735d00691a --- /dev/null +++ b/packages/spector/src/index.ts @@ -0,0 +1 @@ +export * from "./lib/index.js"; diff --git a/packages/spec-lib/src/decorators.ts b/packages/spector/src/lib/decorators.ts similarity index 93% rename from packages/spec-lib/src/decorators.ts rename to packages/spector/src/lib/decorators.ts index 5f0aac830a..3fc1f8c1f0 100644 --- a/packages/spec-lib/src/decorators.ts +++ b/packages/spector/src/lib/decorators.ts @@ -23,16 +23,16 @@ import { ScenarioDecorator, ScenarioDocDecorator, ScenarioServiceDecorator, -} from "../generated-defs/TypeSpec.SpecLib.js"; -import { SpecLibStateKeys } from "./lib.js"; +} from "../../generated-defs/TypeSpec.Spector.js"; +import { SpectorStateKeys } from "./lib.js"; export const $scenario: ScenarioDecorator = (context, target, name?) => { - context.program.stateMap(SpecLibStateKeys.Scenario).set(target, name ?? target.name); + context.program.stateMap(SpectorStateKeys.Scenario).set(target, name ?? target.name); }; export const $scenarioDoc: ScenarioDocDecorator = (context, target, doc, formatArgs?) => { const formattedDoc = formatArgs ? replaceTemplatedStringFromProperties(doc, formatArgs) : doc; - context.program.stateMap(SpecLibStateKeys.ScenarioDoc).set(target, formattedDoc); + context.program.stateMap(SpectorStateKeys.ScenarioDoc).set(target, formattedDoc); }; export const $scenarioService: ScenarioServiceDecorator = (context, target, route, options?) => { @@ -40,7 +40,7 @@ export const $scenarioService: ScenarioServiceDecorator = (context, target, rout type: { kind: "String", value: getNamespaceFullName(target).replace(/\./g, "") }, }); - context.program.stateSet(SpecLibStateKeys.ScenarioService).add(target); + context.program.stateSet(SpectorStateKeys.ScenarioService).add(target); const versions = options ? (options as Model).properties.get("versioned")?.type : null; if (versions) { @@ -63,7 +63,7 @@ export function getScenarioDoc( program: Program, target: Operation | Interface | Namespace, ): string | undefined { - return program.stateMap(SpecLibStateKeys.ScenarioDoc).get(target); + return program.stateMap(SpectorStateKeys.ScenarioDoc).get(target); } function replaceTemplatedStringFromProperties(formatString: string, formatArgs: Model) { @@ -210,14 +210,14 @@ function resolveScenarioName(target: Operation | Interface | Namespace, name: st } export function isScenario(program: Program, target: Operation | Interface | Namespace): boolean { - return program.stateMap(SpecLibStateKeys.Scenario).has(target); + return program.stateMap(SpectorStateKeys.Scenario).has(target); } export function getScenarioName( program: Program, target: Operation | Interface | Namespace, ): string | undefined { - const name = program.stateMap(SpecLibStateKeys.Scenario).get(target); + const name = program.stateMap(SpectorStateKeys.Scenario).get(target); if (name === undefined) { return undefined; } diff --git a/packages/spec-lib/src/index.ts b/packages/spector/src/lib/index.ts similarity index 100% rename from packages/spec-lib/src/index.ts rename to packages/spector/src/lib/index.ts diff --git a/packages/spec-lib/src/lib.ts b/packages/spector/src/lib/lib.ts similarity index 95% rename from packages/spec-lib/src/lib.ts rename to packages/spector/src/lib/lib.ts index 812d9823a2..b4450552ec 100644 --- a/packages/spec-lib/src/lib.ts +++ b/packages/spector/src/lib/lib.ts @@ -1,7 +1,7 @@ import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler"; export const $lib = createTypeSpecLibrary({ - name: "@typespec/spec-lib", + name: "@typespec/spector", diagnostics: { "category-invalid": { severity: "error", @@ -30,4 +30,4 @@ export const $lib = createTypeSpecLibrary({ }, }); -export const { reportDiagnostic, createStateSymbol, stateKeys: SpecLibStateKeys } = $lib; +export const { reportDiagnostic, createStateSymbol, stateKeys: SpectorStateKeys } = $lib; diff --git a/packages/spec-lib/src/tsp-index.ts b/packages/spector/src/lib/tsp-index.ts similarity index 62% rename from packages/spec-lib/src/tsp-index.ts rename to packages/spector/src/lib/tsp-index.ts index bba193fbfe..bf29ae1369 100644 --- a/packages/spec-lib/src/tsp-index.ts +++ b/packages/spector/src/lib/tsp-index.ts @@ -1,12 +1,12 @@ -import type { TypeSpecSpecLibDecorators } from "../generated-defs/TypeSpec.SpecLib.js"; +import type { TypeSpecSpectorDecorators } from "../../generated-defs/TypeSpec.Spector.js"; import { $scenario, $scenarioDoc, $scenarioService } from "./decorators.js"; export { $lib } from "./lib.js"; /** @internal */ export const $decorators = { - "TypeSpec.SpecLib": { + "TypeSpec.Spector": { scenario: $scenario, scenarioDoc: $scenarioDoc, scenarioService: $scenarioService, - } satisfies TypeSpecSpecLibDecorators, + } satisfies TypeSpecSpectorDecorators, }; diff --git a/packages/spec-lib/src/validate.ts b/packages/spector/src/lib/validate.ts similarity index 100% rename from packages/spec-lib/src/validate.ts rename to packages/spector/src/lib/validate.ts diff --git a/packages/spector/src/scenarios-resolver.ts b/packages/spector/src/scenarios-resolver.ts index 32f20211f2..6836b623f4 100644 --- a/packages/spector/src/scenarios-resolver.ts +++ b/packages/spector/src/scenarios-resolver.ts @@ -1,10 +1,10 @@ import { Operation } from "@typespec/compiler"; import { isSharedRoute } from "@typespec/http"; import { ScenarioMockApi } from "@typespec/spec-api"; -import { Scenario } from "@typespec/spec-lib"; import { dirname, join, relative, resolve } from "path"; import pc from "picocolors"; import { pathToFileURL } from "url"; +import type { Scenario } from "./lib/decorators.js"; import { logger } from "./logger.js"; import { importSpecExpect, importTypeSpec, importTypeSpecHttp } from "./spec-utils/index.js"; import { findFilesFromPattern } from "./utils/file-utils.js"; @@ -70,7 +70,7 @@ export async function loadScenarios( for (const { name, specFilePath } of scenarioFiles) { logger.debug(`Found scenario "${specFilePath}"`); const program = await typespecCompiler.compile(typespecCompiler.NodeHost, specFilePath, { - additionalImports: ["@typespec/spec-lib"], + additionalImports: ["@typespec/spector"], noEmit: true, warningAsError: true, }); diff --git a/packages/spector/src/spec-utils/import-spec.ts b/packages/spector/src/spec-utils/import-spec.ts index 9175d0b265..82ce92e201 100644 --- a/packages/spector/src/spec-utils/import-spec.ts +++ b/packages/spector/src/spec-utils/import-spec.ts @@ -1,6 +1,6 @@ +import { type ResolveModuleHost, resolveModule } from "@typespec/compiler/module-resolver"; import { readFile, realpath, stat } from "fs/promises"; import { pathToFileURL } from "url"; -import { ResolveModuleHost, resolveModule } from "./module-resolver.js"; export async function importTypeSpec( baseDir: string, @@ -9,8 +9,8 @@ export async function importTypeSpec( } export async function importSpecExpect( baseDir: string, -): Promise { - return importTypeSpecLibrary("@typespec/spec-lib", baseDir); +): Promise { + return importTypeSpecLibrary("@typespec/spector", baseDir); } export async function importTypeSpecRest( baseDir: string, @@ -33,8 +33,11 @@ export async function importTypeSpecLibrary(name: string, baseDir: string): Prom }; const resolved = await resolveModule(host, name, { baseDir, + conditions: ["import"], }); - return import(pathToFileURL(resolved).toString()); + return import( + pathToFileURL(resolved.type === "module" ? resolved.mainFile : resolved.path).toString() + ); } catch (err: any) { if (err.code === "MODULE_NOT_FOUND") { // Resolution from cwd failed: use current package. diff --git a/packages/spector/src/spec-utils/module-resolver.ts b/packages/spector/src/spec-utils/module-resolver.ts deleted file mode 100644 index 9f32637c1e..0000000000 --- a/packages/spector/src/spec-utils/module-resolver.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { getDirectoryPath, joinPaths, resolvePath } from "@typespec/compiler"; - -export interface ResolveModuleOptions { - baseDir: string; - resolveMain?: (pkg: any) => string; -} - -export interface ResolveModuleHost { - /** - * Resolve the real path for the current host. - */ - realpath(path: string): Promise; - - /** - * Get information about the given path - */ - stat(path: string): Promise<{ isDirectory(): boolean; isFile(): boolean }>; - - /** - * Read a utf-8 encoded file. - */ - readFile(path: string): Promise; -} - -type ResolveModuleErrorCode = "MODULE_NOT_FOUND"; -export class ResolveModuleError extends Error { - public constructor( - public code: ResolveModuleErrorCode, - message: string, - ) { - super(message); - } -} - -/** - * Resolve a module - * @param host - * @param name - * @param options - * @returns - */ -export async function resolveModule( - host: ResolveModuleHost, - name: string, - options: ResolveModuleOptions, -) { - const { baseDir } = options; - const absoluteStart = baseDir === "" ? "." : await host.realpath(resolvePath(baseDir)); - - if (!(await isDirectory(host, absoluteStart))) { - throw new TypeError(`Provided basedir '${baseDir}'is not a directory.`); - } - - // Check if the module name is referencing a path(./foo, /foo, file:/foo) - if (/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/.test(name)) { - const res = resolvePath(absoluteStart, name); - const m = (await loadAsFile(res)) || (await loadAsDirectory(res)); - if (m) return host.realpath(m); - } - - const module = await findAsNodeModule(name, absoluteStart); - if (module) return host.realpath(module); - - throw new ResolveModuleError( - "MODULE_NOT_FOUND", - `Cannot find module '${name} ' from '${baseDir}'`, - ); - - /** - * Returns a list of all the parent directory and the given one. - */ - function listAllParentDirs(baseDir: string): string[] { - const paths = [baseDir]; - let current = getDirectoryPath(baseDir); - while (current !== paths[paths.length - 1]) { - paths.push(current); - current = getDirectoryPath(current); - } - - return paths; - } - - function getPackageCandidates(name: string, baseDir: string) { - const dirs = listAllParentDirs(baseDir); - return dirs.map((x) => joinPaths(x, "node_modules", name)); - } - - async function findAsNodeModule(name: string, baseDir: string): Promise { - const dirs = getPackageCandidates(name, baseDir); - for (const dir of dirs) { - if (await isDirectory(host, dir)) { - const n = await loadAsDirectory(dir); - if (n) return n; - } - } - return undefined; - } - - async function loadAsDirectory(directory: string): Promise { - const pkgFile = resolvePath(directory, "package.json"); - if (await isFile(host, pkgFile)) { - const pkg = await readPackage(host, pkgFile); - const mainFile = options.resolveMain ? options.resolveMain(pkg) : pkg.main; - if (typeof mainFile !== "string") { - throw new TypeError(`package "${pkg.name}" main must be a string but was '${mainFile}'`); - } - - const mainFullPath = resolvePath(directory, mainFile); - try { - return loadAsFile(mainFullPath) ?? loadAsDirectory(mainFullPath); - } catch (e) { - throw new Error( - `Cannot find module '${mainFullPath}'. Please verify that the package.json has a valid "main" entry`, - ); - } - } - - // Try to load index file - return loadAsFile(joinPaths(directory, "index")); - } - - async function loadAsFile(file: string): Promise { - if (await isFile(host, file)) { - return file; - } - - const extensions = [".js"]; - for (const ext of extensions) { - const fileWithExtension = file + ext; - if (await isFile(host, fileWithExtension)) { - return fileWithExtension; - } - } - return undefined; - } -} - -async function readPackage(host: ResolveModuleHost, pkgfile: string) { - const content = await host.readFile(pkgfile); - return JSON.parse(content); -} - -async function isDirectory(host: ResolveModuleHost, path: string) { - try { - const stats = await host.stat(path); - return stats.isDirectory(); - } catch (e: any) { - if (e.code === "ENOENT" || e.code === "ENOTDIR") { - return false; - } - throw e; - } -} - -async function isFile(host: ResolveModuleHost, path: string) { - try { - const stats = await host.stat(path); - return stats.isFile(); - } catch (e: any) { - if (e.code === "ENOENT" || e.code === "ENOTDIR") { - return false; - } - throw e; - } -} diff --git a/packages/spector/tsconfig.build.json b/packages/spector/tsconfig.build.json index edde292acf..0085c31c29 100644 --- a/packages/spector/tsconfig.build.json +++ b/packages/spector/tsconfig.build.json @@ -1,12 +1,13 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "rootDir": "./src" + "outDir": "dist", + "tsBuildInfoFile": "temp/.tsbuildinfo" }, "references": [ - { "path": "../spec-lib/tsconfig.build.json" }, { "path": "../spec-api/tsconfig.build.json" }, { "path": "../spec-coverage-sdk/tsconfig.build.json" } ], - "exclude": ["**/*.test.*", "test/**/*"] + "include": ["src/**/*.ts", "generated-defs/**/*.ts"], + "exclude": ["**/*.test.*"] } diff --git a/packages/spector/tsconfig.json b/packages/spector/tsconfig.json index 4c0d25c1d4..79fb341f39 100644 --- a/packages/spector/tsconfig.json +++ b/packages/spector/tsconfig.json @@ -1,8 +1,4 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "tsBuildInfoFile": "temp/.tsbuildinfo" - }, - "include": ["src/**/*.ts", "test/**/*.ts", "config/config.ts"] + "compilerOptions": {} } diff --git a/packages/website-astro/package.json b/packages/website-astro/package.json index 573177e6e5..decc253800 100644 --- a/packages/website-astro/package.json +++ b/packages/website-astro/package.json @@ -27,7 +27,7 @@ "@fluentui/react-icons": "^2.0.260", "@typespec/compiler": "workspace:~", "@typespec/playground": "workspace:~", - "astro": "^4.16.0", + "astro": "^4.16.1", "clsx": "^2.1.1", "es-module-shims": "~1.10.0", "prism-react-renderer": "^2.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26fb838754..d7248a9be7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -620,9 +620,6 @@ importers: '@typespec/spector': specifier: workspace:~ version: link:../spector - '@typespec/spec-lib': - specifier: workspace:~ - version: link:../spec-lib '@typespec/versioning': specifier: workspace:~ version: link:../versioning @@ -1484,6 +1481,25 @@ importers: specifier: ^2.1.2 version: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(happy-dom@15.7.4)(jsdom@25.0.1)(terser@5.34.1) + packages/spec-coverage-sdk: + dependencies: + '@azure/identity': + specifier: ~4.4.1 + version: 4.4.1 + '@azure/storage-blob': + specifier: ~12.25.0 + version: 12.25.0 + '@types/node': + specifier: ~22.7.5 + version: 22.7.5 + devDependencies: + rimraf: + specifier: ~6.0.1 + version: 6.0.1 + typescript: + specifier: ~5.6.3 + version: 5.6.3 + packages/spector: dependencies: '@azure/identity': @@ -1507,9 +1523,9 @@ importers: '@typespec/spec-coverage-sdk': specifier: workspace:~ version: link:../spec-coverage-sdk - '@typespec/spec-lib': + '@typespec/versioning': specifier: workspace:~ - version: link:../spec-lib + version: link:../versioning ajv: specifier: ~8.17.1 version: 8.17.1 @@ -1592,50 +1608,6 @@ importers: '@types/yargs': specifier: ~17.0.33 version: 17.0.33 - rimraf: - specifier: ~6.0.1 - version: 6.0.1 - typescript: - specifier: ~5.6.3 - version: 5.6.3 - - packages/spec-coverage-sdk: - dependencies: - '@azure/identity': - specifier: ~4.4.1 - version: 4.4.1 - '@azure/storage-blob': - specifier: ~12.25.0 - version: 12.25.0 - '@types/node': - specifier: ~22.7.5 - version: 22.7.5 - devDependencies: - rimraf: - specifier: ~6.0.1 - version: 6.0.1 - typescript: - specifier: ~5.6.3 - version: 5.6.3 - - packages/spec-lib: - dependencies: - '@typespec/compiler': - specifier: workspace:~ - version: link:../compiler - '@typespec/http': - specifier: workspace:~ - version: link:../http - '@typespec/rest': - specifier: workspace:~ - version: link:../rest - '@typespec/versioning': - specifier: workspace:~ - version: link:../versioning - devDependencies: - '@types/node': - specifier: ~22.7.5 - version: 22.7.5 '@typespec/tspd': specifier: workspace:~ version: link:../tspd @@ -2058,7 +2030,7 @@ importers: version: 3.6.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@5.4.8(@types/node@22.7.5)(terser@5.34.1)) '@astrojs/starlight': specifier: ^0.28.3 - version: 0.28.3(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + version: 0.28.3(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) '@docsearch/css': specifier: ^3.6.2 version: 3.6.2 @@ -2081,8 +2053,8 @@ importers: specifier: workspace:~ version: link:../playground astro: - specifier: ^4.16.0 - version: 4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + specifier: ^4.16.1 + version: 4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -6705,8 +6677,8 @@ packages: peerDependencies: astro: ^4.0.0-beta || ^3.3.0 - astro@4.16.0: - resolution: {integrity: sha512-R5voBFy0yOg57uFnW24WV+RvqPerp9eOoDQoT0pQYqECuGuyV1PsZaSb9Nm0ec+KMLrfO9jvvESFw9LIN6XiUw==} + astro@4.16.1: + resolution: {integrity: sha512-ZeZd+L147HHgHmvoSkve7KM3EutV+hY0mOCa4PwARHEFAAh+omo4MUNoTWsFkfq7ozTgR0PCXQwslrZduoWHNg==} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true @@ -8176,9 +8148,6 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - es-module-lexer@1.5.0: - resolution: {integrity: sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==} - es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} @@ -13755,10 +13724,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - yocto-queue@1.1.1: resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} engines: {node: '>=12.20'} @@ -13988,12 +13953,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@3.1.8(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': + '@astrojs/mdx@3.1.8(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': dependencies: '@astrojs/markdown-remark': 5.3.0 '@mdx-js/mdx': 3.0.1 acorn: 8.12.1 - astro: 4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + astro: 4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) es-module-lexer: 1.5.4 estree-util-visit: 2.0.0 gray-matter: 4.0.3 @@ -14030,15 +13995,15 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.23.8 - '@astrojs/starlight@0.28.3(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': + '@astrojs/starlight@0.28.3(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': dependencies: - '@astrojs/mdx': 3.1.8(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + '@astrojs/mdx': 3.1.8(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) '@astrojs/sitemap': 3.2.0 '@pagefind/default-ui': 1.1.1 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - astro: 4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) - astro-expressive-code: 0.35.6(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + astro: 4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + astro-expressive-code: 0.35.6(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.2 @@ -14318,7 +14283,7 @@ snapshots: '@babel/helper-member-expression-to-functions@7.24.5': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 '@babel/helper-module-imports@7.24.7': dependencies: @@ -14346,7 +14311,7 @@ snapshots: '@babel/helper-optimise-call-expression@7.22.5': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 '@babel/helper-plugin-utils@7.24.7': {} @@ -14375,7 +14340,7 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers@7.22.5': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 '@babel/helper-split-export-declaration@7.24.7': dependencies: @@ -14492,7 +14457,7 @@ snapshots: '@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.25.8)': dependencies: '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-syntax-jsx@7.25.7(@babel/core@7.25.8)': dependencies: @@ -14542,7 +14507,7 @@ snapshots: '@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.25.8)': dependencies: '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.25.8)': dependencies: @@ -14805,7 +14770,7 @@ snapshots: '@babel/plugin-transform-react-display-name@7.24.1(@babel/core@7.25.8)': dependencies: '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-transform-react-jsx-development@7.22.5(@babel/core@7.25.8)': dependencies: @@ -14839,7 +14804,7 @@ snapshots: dependencies: '@babel/core': 7.25.8 '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-transform-regenerator@7.24.1(@babel/core@7.25.8)': dependencies: @@ -14895,7 +14860,7 @@ snapshots: '@babel/core': 7.25.8 '@babel/helper-annotate-as-pure': 7.25.7 '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.25.8) '@babel/plugin-transform-unicode-escapes@7.24.1(@babel/core@7.25.8)': @@ -18647,7 +18612,7 @@ snapshots: '@shikijs/engine-javascript': 1.21.0 '@shikijs/engine-oniguruma': 1.21.0 '@shikijs/types': 1.21.0 - '@shikijs/vscode-textmate': 9.2.2 + '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 hast-util-to-html: 9.0.3 @@ -18663,7 +18628,7 @@ snapshots: '@shikijs/engine-javascript@1.21.0': dependencies: '@shikijs/types': 1.21.0 - '@shikijs/vscode-textmate': 9.2.2 + '@shikijs/vscode-textmate': 9.3.0 oniguruma-to-js: 0.4.3 '@shikijs/engine-javascript@1.22.0': @@ -18675,7 +18640,7 @@ snapshots: '@shikijs/engine-oniguruma@1.21.0': dependencies: '@shikijs/types': 1.21.0 - '@shikijs/vscode-textmate': 9.2.2 + '@shikijs/vscode-textmate': 9.3.0 '@shikijs/engine-oniguruma@1.22.0': dependencies: @@ -18684,7 +18649,7 @@ snapshots: '@shikijs/types@1.21.0': dependencies: - '@shikijs/vscode-textmate': 9.2.2 + '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 '@shikijs/types@1.22.0': @@ -18809,7 +18774,7 @@ snapshots: dependencies: '@babel/core': 7.25.8 '@babel/preset-env': 7.24.5(@babel/core@7.25.8) - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 '@storybook/core': 8.3.5 '@storybook/csf': 0.1.11 '@types/cross-spawn': 6.0.6 @@ -20286,12 +20251,12 @@ snapshots: astring@1.8.6: {} - astro-expressive-code@0.35.6(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)): + astro-expressive-code@0.35.6(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)): dependencies: - astro: 4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + astro: 4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) rehype-expressive-code: 0.35.6 - astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3): + astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3): dependencies: '@astrojs/compiler': 2.10.3 '@astrojs/internal-helpers': 0.4.1 @@ -20323,7 +20288,6 @@ snapshots: esbuild: 0.21.5 estree-walker: 3.0.3 fast-glob: 3.3.2 - fastq: 1.17.1 flattie: 1.1.1 github-slugger: 2.0.0 gray-matter: 4.0.3 @@ -20344,7 +20308,6 @@ snapshots: rehype: 13.0.2 semver: 7.6.3 shiki: 1.22.0 - string-width: 7.2.0 tinyexec: 0.3.0 tsconfck: 3.1.4(typescript@5.6.3) unist-util-visit: 5.0.0 @@ -22149,8 +22112,6 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.0.0 - es-module-lexer@1.5.0: {} - es-module-lexer@1.5.4: {} es-module-shims@1.10.0: {} @@ -25680,7 +25641,7 @@ snapshots: p-limit@4.0.0: dependencies: - yocto-queue: 1.0.0 + yocto-queue: 1.1.1 p-limit@6.1.0: dependencies: @@ -28760,7 +28721,7 @@ snapshots: browserslist: 4.23.2 chrome-trace-event: 1.0.3 enhanced-resolve: 5.16.0 - es-module-lexer: 1.5.0 + es-module-lexer: 1.5.4 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -29071,8 +29032,6 @@ snapshots: yocto-queue@0.1.0: {} - yocto-queue@1.0.0: {} - yocto-queue@1.1.1: {} zod-to-json-schema@3.23.3(zod@3.23.8): diff --git a/tsconfig.ws.json b/tsconfig.ws.json index 3767a3d6ff..68c7f0f897 100644 --- a/tsconfig.ws.json +++ b/tsconfig.ws.json @@ -15,7 +15,6 @@ { "path": "packages/openapi3/tsconfig.json" }, { "path": "packages/spec-api/tsconfig.build.json" }, { "path": "packages/spector/tsconfig.build.json" }, - { "path": "packages/spec-lib/tsconfig.build.json" }, { "path": "packages/http-specs/tsconfig.build.json" }, { "path": "packages/monarch/tsconfig.json" }, { "path": "packages/bundler/tsconfig.json" },