diff --git a/AutoRest.sln.DotSettings b/AutoRest.sln.DotSettings index a16e1edd39e62..0a190f6536fe7 100644 --- a/AutoRest.sln.DotSettings +++ b/AutoRest.sln.DotSettings @@ -3,4 +3,5 @@ Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. See License.txt in the project root for license information. - \ No newline at end of file + + False \ No newline at end of file diff --git a/src/core/AutoRest.Core/ClientModel/KnownFormat.cs b/src/core/AutoRest.Core/ClientModel/KnownFormat.cs new file mode 100644 index 0000000000000..c828c19bf8996 --- /dev/null +++ b/src/core/AutoRest.Core/ClientModel/KnownFormat.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// + +using System.Diagnostics.CodeAnalysis; + +namespace AutoRest.Core.ClientModel +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum KnownFormat + { + none, + unknown, + + @char, + int32, + int64, + @float, + @double, + @byte, + binary, + date, + date_time, + password, + date_time_rfc1123, + duration, + uuid, + base64url, + @decimal, + unixtime + } +} \ No newline at end of file diff --git a/src/core/AutoRest.Core/ClientModel/KnownFormatExtensions.cs b/src/core/AutoRest.Core/ClientModel/KnownFormatExtensions.cs new file mode 100644 index 0000000000000..a4fc0d42fa06f --- /dev/null +++ b/src/core/AutoRest.Core/ClientModel/KnownFormatExtensions.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// + +using System; + +namespace AutoRest.Core.ClientModel +{ + public static class KnownFormatExtensions + { + public static KnownFormat Parse(string formatValue) + { + if (string.IsNullOrWhiteSpace(formatValue)) + { + return KnownFormat.none; + } + + KnownFormat result; + return Enum.TryParse(formatValue.Replace('-', '_'), true, out result) ? result : KnownFormat.unknown; + } + } +} \ No newline at end of file diff --git a/src/core/AutoRest.Core/ClientModel/PrimaryType.cs b/src/core/AutoRest.Core/ClientModel/PrimaryType.cs index 70d9de5cdc65f..c7025d56957fc 100644 --- a/src/core/AutoRest.Core/ClientModel/PrimaryType.cs +++ b/src/core/AutoRest.Core/ClientModel/PrimaryType.cs @@ -37,6 +37,12 @@ public PrimaryType(KnownPrimaryType type) /// public string Format { get; set; } + /// + /// Returns the KnownFormat of the Format string (provided it matches a KnownFormat) + /// Otherwise, returns KnownFormat.none + /// + public KnownFormat KnownFormat => KnownFormatExtensions.Parse(Format); + /// /// Returns a string representation of the PrimaryType object. /// diff --git a/src/core/AutoRest.Core/Logging/Logger.cs b/src/core/AutoRest.Core/Logging/Logger.cs index c0e1e53a76e6e..def62f47459e2 100644 --- a/src/core/AutoRest.Core/Logging/Logger.cs +++ b/src/core/AutoRest.Core/Logging/Logger.cs @@ -34,7 +34,9 @@ static Logger() /// Optional arguments to use if message includes formatting. public static void LogInfo(string message, params object[] args) { + lock( typeof( Logger ) ) { Entries.Add(new LogEntry(LogEntrySeverity.Info, string.Format(CultureInfo.InvariantCulture, message, args))); + } } /// diff --git a/src/generator/AutoRest.CSharp.Unit.Tests/AutoDynamic.cs b/src/generator/AutoRest.CSharp.Unit.Tests/AutoDynamic.cs new file mode 100644 index 0000000000000..17d4b103c3cc2 --- /dev/null +++ b/src/generator/AutoRest.CSharp.Unit.Tests/AutoDynamic.cs @@ -0,0 +1,258 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// + +using System; +using System.Collections; +using System.Diagnostics; +using System.Dynamic; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; + +namespace AutoRest.CSharp.Unit.Tests +{ + /// + /// This is a class that creates a dynamic wrapper around any object and can allow + /// deep inspection of anything (private or otherwise). + /// Handy for testing. + /// + public class AutoDynamic : DynamicObject + { + /// + /// Specify the flags for accessing members + /// + private static readonly BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance + | BindingFlags.Static | BindingFlags.Public + | BindingFlags.IgnoreCase; + + /// + /// The object we are going to wrap + /// + private readonly object _wrapped; + + /// + /// Create a simple private wrapper + /// + public AutoDynamic(object o) + { + _wrapped = o; + } + + /// + /// Returns a JSON representation. + /// + /// a JSON string + public string ToJson() + { + return JsonConvert.SerializeObject( + _wrapped, + Formatting.Indented, + new JsonSerializerSettings + { + Converters = new JsonConverter[] {new StringEnumConverter()}, + ContractResolver = new CamelCasePropertyNamesContractResolver(), + NullValueHandling = NullValueHandling.Ignore, + ObjectCreationHandling = ObjectCreationHandling.Reuse + }); + } + + private bool CheckResult(object result, out object outresult) + { + if (result == null || result.GetType().GetTypeInfo().IsPrimitive + || result.GetType().GetTypeInfo().IsValueType || result is string) + { + outresult = result; + } + else + { + outresult = result.ToDynamic(); + } + + return true; + } + + /// + /// Try invoking a method + /// + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + if (_wrapped == null) + { + result = null; + return true; + } + var types = args.Select(a => a != null ? a.GetType() : typeof(object)); + + var method = _wrapped.GetType().GetMethod(binder.Name, types.ToArray()) + ?? _wrapped.GetType().GetMethod(binder.Name, flags); + + if (method == null) + { + return base.TryInvokeMember(binder, args, out result); + } + + return CheckResult(method.Invoke(_wrapped, args), out result); + } + + public override bool TryConvert(ConvertBinder binder, out object result) + { + if (_wrapped == null) + { + result = null; + return false; + } + + if (binder.ReturnType.IsInstanceOfType(_wrapped)) + { + result = _wrapped; + return true; + } + return base.TryConvert(binder, out result); + } + + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + { + if (_wrapped == null) + { + result = null; + return false; + } + + if (indexes.Length == 1 && indexes[0] is int) + { + var index = (int) indexes[0]; + try + { + var arr = _wrapped as Array; + if (arr != null) + { + return CheckResult(arr.GetValue(index), out result); + } + } + catch + { + // nope... + } + } + + // is it asking for a property as a field + foreach ( + var prop in _wrapped.GetType().GetProperties(flags).Where(each => each.GetIndexParameters().Any())) + { + try + { + result = prop.GetValue(_wrapped, indexes); + return true; + } + catch (TargetParameterCountException) + { + } + catch (TargetInvocationException) + { + } + } + + if (indexes.Length == 1 && indexes[0] is int) + { + var index = (int) indexes[0]; + try + { + var ie = _wrapped as IEnumerable; + if (ie != null) + { + var e = ie.GetEnumerator(); + + while (index > 0 && e.MoveNext()) + { + --index; + } + if (index == 0) + { + if (e.MoveNext()) + { + return CheckResult(e.Current, out result); + } + } + } + } + catch + { + } + } + return base.TryGetIndex(binder, indexes, out result); + } + + /// + /// Tries to get a property or field with the given name + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + if (_wrapped == null) + { + result = null; + return true; + } + + try + { + //Try getting a property of that name + var prop = _wrapped.GetType().GetProperty(binder.Name, flags); + + if (prop == null) + { + //Try getting a field of that name + var fld = _wrapped.GetType().GetField(binder.Name, flags); + + if (fld != null) + { + return CheckResult(fld.GetValue(_wrapped), out result); + } + + // check if this is an index into the + if (TryGetIndex(null, new[] {binder.Name}, out result)) + { + return true; + } + + return base.TryGetMember(binder, out result); + } + return CheckResult(prop.GetValue(_wrapped, null), out result); + } + catch (Exception e) + { + Debug.WriteLine($"{e.Message}/{e.StackTrace}"); + result = null; + return true; + } + } + + /// + /// Tries to set a property or field with the given name + /// + public override bool TrySetMember(SetMemberBinder binder, object value) + { + if (_wrapped == null) + { + return false; + } + + var prop = _wrapped.GetType().GetProperty(binder.Name, flags); + if (prop == null) + { + var fld = _wrapped.GetType().GetField(binder.Name, flags); + if (fld != null) + { + fld.SetValue(_wrapped, value); + return true; + } + return base.TrySetMember(binder, value); + } + + prop.SetValue(_wrapped, value, null); + return true; + } + } +} \ No newline at end of file diff --git a/src/generator/AutoRest.CSharp.Unit.Tests/Bug1125.cs b/src/generator/AutoRest.CSharp.Unit.Tests/Bug1125.cs new file mode 100644 index 0000000000000..c0c4fe4846886 --- /dev/null +++ b/src/generator/AutoRest.CSharp.Unit.Tests/Bug1125.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// + +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Xunit; +using Xunit.Abstractions; + +namespace AutoRest.CSharp.Unit.Tests +{ + public class Bug1125 : BugTest + { + public Bug1125(ITestOutputHelper output) : base(output) + { + } + + /// + /// https://github.com/Azure/autorest/issues/1125 + /// Support format:'char' for single character strings. + /// + [Fact] + public async Task SupportCharFormatForString() + { + // simplified test pattern for unit testing aspects of code generation + using (var fileSystem = GenerateCodeForTestFromSpec()) + { + // Expected Files + Assert.True(fileSystem.FileExists(@"GeneratedCode\Models\ResultObject.cs")); + Assert.True(fileSystem.FileExists(@"GeneratedCode\Models\ResultObjectWithDefault.cs")); + Assert.True(fileSystem.FileExists(@"GeneratedCode\Models\ResultObjectWithMinMax.cs")); + Assert.True(fileSystem.FileExists(@"GeneratedCode\Models\ResultObjectWithExclusiveMinMax.cs")); + Assert.True(fileSystem.FileExists(@"GeneratedCode\Models\ParamObject.cs")); + Assert.True(fileSystem.FileExists(@"GeneratedCode\Models\ParamObjectWithDefault.cs")); + + var result = await Compile(fileSystem); + + // filter the warnings + var warnings = result.Messages.Where( + each => each.Severity == DiagnosticSeverity.Warning + && !SuppressWarnings.Contains(each.Id)).ToArray(); + + // use this to dump the files to disk for examination + // fileSystem.SaveFilesToTemp("bug1125"); + + // filter the errors + var errors = result.Messages.Where(each => each.Severity == DiagnosticSeverity.Error).ToArray(); + + Write(warnings, fileSystem); + Write(errors, fileSystem); + + // use this to write out all the messages, even hidden ones. + // Write(result.Messages, fileSystem); + + // Don't proceed unless we have zero Warnings. + Assert.Empty(warnings); + + // Don't proceed unless we have zero Errors. + Assert.Empty(errors); + + // Should also succeed. + Assert.True(result.Succeeded); + + // try to load the assembly + var asm = Assembly.Load(result.Output.GetBuffer()); + Assert.NotNull(asm); + + // verify that we have the class we expected + var resultObject = asm.ExportedTypes.FirstOrDefault(each => each.FullName == "Test.Models.ResultObject"); + Assert.NotNull(resultObject); + + // verify the property is generated + var property = resultObject.GetProperty("SingleLetter"); + Assert.NotNull(property); + + // verify the type is as expected. + Assert.Equal(property.PropertyType, typeof(char?)); + } + } + } +} \ No newline at end of file diff --git a/src/generator/AutoRest.CSharp.Unit.Tests/BugTest.cs b/src/generator/AutoRest.CSharp.Unit.Tests/BugTest.cs index b1815563e740a..3b2064b5d25c8 100644 --- a/src/generator/AutoRest.CSharp.Unit.Tests/BugTest.cs +++ b/src/generator/AutoRest.CSharp.Unit.Tests/BugTest.cs @@ -1,24 +1,119 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +// +using System; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; -using AutoRest.Core.Logging; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; using AutoRest.Core.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.Rest.CSharp.Compiler.Compilation; +using Xunit.Abstractions; +using OutputKind = Microsoft.Rest.CSharp.Compiler.Compilation.OutputKind; namespace AutoRest.CSharp.Unit.Tests { public class BugTest { + private ITestOutputHelper _output; + internal static string[] SuppressWarnings = {"CS1701"}; + + public BugTest(ITestOutputHelper output) + { + _output = output; + } + public BugTest() { - Logger.Entries.Clear(); } - - protected MemoryFileSystem CreateMockFilesystem() + + protected virtual MemoryFileSystem CreateMockFilesystem() { var fs = new MemoryFileSystem(); fs.Copy(Path.Combine("Resource", "AutoRest.json")); return fs; } + + protected virtual MemoryFileSystem GenerateCodeForTestFromSpec() + { + var fs = CreateMockFilesystem(); + $"{GetType().Name}.yaml".GenerateCodeInto(fs); + return fs; + } + + protected virtual void WriteLine(object value) + { + if (value != null) + { + _output?.WriteLine(value.ToString()); + Debug.WriteLine(value.ToString()); + } + else + { + _output?.WriteLine(""); + Debug.WriteLine(""); + } + } + + protected virtual void WriteLine(string format, params object[] values) + { + if (format != null) + { + if (values != null && values.Length > 0) + { + _output?.WriteLine(format, values); + Debug.WriteLine(format, values); + } + else + { + _output?.WriteLine(format); + Debug.WriteLine(format); + } + } + else + { + _output?.WriteLine(""); + Debug.WriteLine(""); + } + } + + protected void Write(IEnumerable messages, MemoryFileSystem fileSystem) + { + if (messages.Any()) + { + foreach (var file in messages.GroupBy(each => each.Location?.SourceTree?.FilePath, each => each)) + { + var text = file.Key != null ? fileSystem.VirtualStore[file.Key].ToString() : string.Empty; + + foreach (var error in file) + { + WriteLine(error.ToString()); + // WriteLine(text.Substring(error.Location.SourceSpan.Start, error.Location.SourceSpan.Length)); + } + } + } + } + + protected async Task Compile(IFileSystem fileSystem) + { + var compiler = new CSharpCompiler( + fileSystem.GetFiles("GeneratedCode", "*.cs", SearchOption.AllDirectories) + .Select(each => new KeyValuePair(each, fileSystem.ReadFileAsText(each))).ToArray(), + ManagedAssets.FrameworkAssemblies.Concat( + AppDomain.CurrentDomain.GetAssemblies() + .Where(each => !each.IsDynamic) + .Select(each => each.Location) + .Concat(new[] + { + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + "Microsoft.Rest.ClientRuntime.dll") + }) + )); + return await compiler.Compile(OutputKind.DynamicallyLinkedLibrary); + } } } \ No newline at end of file diff --git a/src/generator/AutoRest.CSharp.Unit.Tests/Resource/Bug1125.yaml b/src/generator/AutoRest.CSharp.Unit.Tests/Resource/Bug1125.yaml new file mode 100644 index 0000000000000..2bd46f07c1c15 --- /dev/null +++ b/src/generator/AutoRest.CSharp.Unit.Tests/Resource/Bug1125.yaml @@ -0,0 +1,200 @@ +swagger: '2.0' +info: + version: 1.0.0 + title: Simple API +paths: + /operation: + get: + operationId: my_operation + responses: + 200: + description: OK + schema: + $ref: '#/definitions/ResultObject' + + /operation2: + get: + operationId: my_operation2 + responses: + 200: + description: OK + schema: + $ref: '#/definitions/ResultObjectWithDefault' + + '/operation3/{letter}': + put: + operationId: my_operation3 + parameters: + - name: letter + in: path + required: true + type: string + format: char + description: a single letter + + responses: + 200: + description: OK + schema: + $ref: '#/definitions/ResultObject' + + '/operation4int/{anint}': + put: + operationId: my_operation4withint + parameters: + - name: anint + in: path + required: false + type: integer + format: int32 + description: a single int + + responses: + 200: + description: OK + schema: + $ref: '#/definitions/ResultObject' + + + '/operation4/{letter}': + put: + operationId: my_operation4 + parameters: + - name: letter + in: path + required: false + type: string + format: char + description: a single letter that is optional + + responses: + 200: + description: OK + schema: + $ref: '#/definitions/ResultObject' + + /operation5: + put: + operationId: my_operation5 + parameters: + - name: someparams + in: body + required: true + schema: + $ref: '#/definitions/ParamObject' + + responses: + 200: + description: OK + schema: + $ref: '#/definitions/ResultObject' + + /operation6: + put: + operationId: my_operation6 + parameters: + - name: someparams + in: body + required: true + schema: + $ref: '#/definitions/ParamObjectWithDefault' + + responses: + 200: + description: OK + schema: + $ref: '#/definitions/ResultObject' + + '/operation7/{letter}': + get: + operationId: my_operation7 + parameters: + - name: letter + in: path + required: true + type: string + format: char + minimum: a + maximum: z + description: a single letter that is between a and z (inclusive) + + responses: + 200: + description: OK + schema: + $ref: '#/definitions/ResultObjectWithMinMax' + + '/operation8/{letter}': + get: + operationId: my_operation8 + parameters: + - name: letter + in: path + required: true + type: string + format: char + minimum: a + maximum: z + exclusiveMinimum: true + exclusiveMaximum: true + description: a single letter that is between a and z (exclusive) + + responses: + 200: + description: OK + schema: + $ref: '#/definitions/ResultObjectWithExclusiveMinMax' + + +definitions: + ResultObject: + properties: + SingleLetter: + type: string + format: char + description: 'This should be a char.' + + ResultObjectWithDefault: + properties: + SingleLetterWithDefault: + type: string + format: char + default: x + description: 'This should be a char and the default should be x.' + + ResultObjectWithMinMax: + properties: + SingleLetterWithDefault: + type: string + format: char + minimum: a + maximum: z + description: 'This should be a char and the default should be between a and z (inclusive).' + + ResultObjectWithExclusiveMinMax: + properties: + SingleLetterWithDefault: + type: string + format: char + exclusiveMinimum: true + exclusiveMaximum: true + minimum: a + maximum: z + description: 'This should be a char and the default should be between a and z. (exclusive)' + + ParamObject: + properties: + SingleLetter: + type: string + format: char + default: x + description: 'This should be a char' + + ParamObjectWithDefault: + properties: + SingleLetterWithDefault: + type: string + format: char + default: x + description: 'This should be a char and the default should be x.' + diff --git a/src/generator/AutoRest.CSharp.Unit.Tests/TestExtensions.cs b/src/generator/AutoRest.CSharp.Unit.Tests/TestExtensions.cs index 430e48b3e1368..8392343664d3f 100644 --- a/src/generator/AutoRest.CSharp.Unit.Tests/TestExtensions.cs +++ b/src/generator/AutoRest.CSharp.Unit.Tests/TestExtensions.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +// +using System; using System.IO; using AutoRest.Core; using AutoRest.Core.Extensibility; @@ -10,16 +12,32 @@ namespace AutoRest.CSharp.Unit.Tests { internal static class TestExtensions { - internal static void Copy(this IFileSystem fileSystem , string sourceFileOnDisk) + public static dynamic ToDynamic(this object anonymousObject) { - Copy( fileSystem, sourceFileOnDisk, Path.GetFileName(sourceFileOnDisk)); + return new AutoDynamic(anonymousObject); } + + public static bool IsAnonymousType(this Type type) + { + var name = type.Name; + if (name.Length < 3) + { + return false; + } + return name[0] == '<' && name[1] == '>' && name.IndexOf("AnonymousType", StringComparison.Ordinal) > 0; + } + + internal static void Copy(this IFileSystem fileSystem, string sourceFileOnDisk) + { + Copy(fileSystem, sourceFileOnDisk, Path.GetFileName(sourceFileOnDisk)); + } + internal static void Copy(this IFileSystem fileSystem, string sourceFileOnDisk, string destination) { fileSystem.WriteFile(destination, File.ReadAllText(sourceFileOnDisk)); } - internal static MemoryFileSystem GenerateCodeInto(this string inputFile, MemoryFileSystem fileSystem ) + internal static MemoryFileSystem GenerateCodeInto(this string inputFile, MemoryFileSystem fileSystem) { fileSystem.Copy(Path.Combine("Resource", inputFile)); @@ -29,16 +47,42 @@ internal static MemoryFileSystem GenerateCodeInto(this string inputFile, MemoryF CodeGenerator = "CSharp", FileSystem = fileSystem, OutputDirectory = "GeneratedCode", - Input = inputFile, + Namespace = "Test", + Input = inputFile }; var codeGenerator = new CSharpCodeGenerator(settings); - Modeler modeler = ExtensionsLoader.GetModeler(settings); + var modeler = ExtensionsLoader.GetModeler(settings); var sc = modeler.Build(); codeGenerator.NormalizeClientModel(sc); codeGenerator.Generate(sc).GetAwaiter().GetResult(); return fileSystem; } + + internal static void SaveFilesToTemp(this IFileSystem fileSystem, string folderName = null) + { + folderName = string.IsNullOrWhiteSpace(folderName) ? Guid.NewGuid().ToString() : folderName; + var outputFolder = Path.Combine(Path.GetTempPath(), folderName); + if (Directory.Exists(outputFolder)) + { + try + { + Directory.Delete(outputFolder, true); + } + catch + { + // who cares... + } + } + + Directory.CreateDirectory(outputFolder); + foreach (var file in fileSystem.GetFiles("", "*", SearchOption.AllDirectories)) + { + var target = Path.Combine(outputFolder, file.Substring(file.IndexOf(":", StringComparison.Ordinal) + 1)); + Directory.CreateDirectory(Path.GetDirectoryName(target)); + File.WriteAllText(target, fileSystem.ReadFileAsText(file)); + } + } } } \ No newline at end of file diff --git a/src/generator/AutoRest.CSharp.Unit.Tests/project.json b/src/generator/AutoRest.CSharp.Unit.Tests/project.json index 347aab23721eb..a3068b920daff 100644 --- a/src/generator/AutoRest.CSharp.Unit.Tests/project.json +++ b/src/generator/AutoRest.CSharp.Unit.Tests/project.json @@ -14,7 +14,7 @@ "keyFile": "../../../Tools/MSSharedLibKey.snk", "copyToOutput": { - "include": ["Resource"], + "include": ["Resource"] } }, diff --git a/src/generator/AutoRest.CSharp/CSharpCodeNamer.cs b/src/generator/AutoRest.CSharp/CSharpCodeNamer.cs index 7d4f9b313d98f..6ef0116ac0e37 100644 --- a/src/generator/AutoRest.CSharp/CSharpCodeNamer.cs +++ b/src/generator/AutoRest.CSharp/CSharpCodeNamer.cs @@ -234,78 +234,69 @@ public override IType NormalizeTypeDeclaration(IType type) protected virtual IType NormalizePrimaryType(PrimaryType primaryType) { - if (primaryType == null) - { - return null; - } - - if (primaryType.Type == KnownPrimaryType.Base64Url) + switch (primaryType?.Type) { + case KnownPrimaryType.Base64Url: primaryType.Name = "byte[]"; - } - else if (primaryType.Type == KnownPrimaryType.Boolean) - { + break; + case KnownPrimaryType.Boolean: primaryType.Name = "bool"; - } - else if (primaryType.Type == KnownPrimaryType.ByteArray) - { + break; + case KnownPrimaryType.ByteArray: primaryType.Name = "byte[]"; - } - else if (primaryType.Type == KnownPrimaryType.Date) - { + break; + case KnownPrimaryType.Date: primaryType.Name = "DateTime"; - } - else if (primaryType.Type == KnownPrimaryType.DateTime) - { + break; + case KnownPrimaryType.DateTime: primaryType.Name = UseDateTimeOffset ? "DateTimeOffset" : "DateTime"; - } - else if (primaryType.Type == KnownPrimaryType.DateTimeRfc1123) - { + break; + case KnownPrimaryType.DateTimeRfc1123: primaryType.Name = "DateTime"; - } - else if (primaryType.Type == KnownPrimaryType.Double) - { + break; + case KnownPrimaryType.Double: primaryType.Name = "double"; - } - else if (primaryType.Type == KnownPrimaryType.Decimal) - { + break; + case KnownPrimaryType.Decimal: primaryType.Name = "decimal"; - } - else if (primaryType.Type == KnownPrimaryType.Int) - { + break; + case KnownPrimaryType.Int: primaryType.Name = "int"; - } - else if (primaryType.Type == KnownPrimaryType.Long) - { + break; + case KnownPrimaryType.Long: primaryType.Name = "long"; - } - else if (primaryType.Type == KnownPrimaryType.Stream) - { + break; + case KnownPrimaryType.Stream: primaryType.Name = "System.IO.Stream"; - } - else if (primaryType.Type == KnownPrimaryType.String) + break; + case KnownPrimaryType.String: + switch (KnownFormatExtensions.Parse( primaryType.Format ) ) { + case KnownFormat.@char: + primaryType.Name = "char"; + break; + + default: primaryType.Name = "string"; + break; } - else if (primaryType.Type == KnownPrimaryType.TimeSpan) - { + + break; + case KnownPrimaryType.TimeSpan: primaryType.Name = "TimeSpan"; - } - else if (primaryType.Type == KnownPrimaryType.Object) - { + break; + case KnownPrimaryType.Object: primaryType.Name = "object"; - } - else if (primaryType.Type == KnownPrimaryType.Credentials) - { + break; + case KnownPrimaryType.Credentials: primaryType.Name = "ServiceClientCredentials"; - } - else if (primaryType.Type == KnownPrimaryType.UnixTime) - { + break; + case KnownPrimaryType.UnixTime: primaryType.Name = "DateTime"; - } - else if (primaryType.Type == KnownPrimaryType.Uuid) - { + break; + case KnownPrimaryType.Uuid: primaryType.Name = "Guid"; + break; } return primaryType; diff --git a/src/generator/AutoRest.CSharp/ClientModelExtensions.cs b/src/generator/AutoRest.CSharp/ClientModelExtensions.cs index faf83b945610c..f3e4a4c0e8271 100644 --- a/src/generator/AutoRest.CSharp/ClientModelExtensions.cs +++ b/src/generator/AutoRest.CSharp/ClientModelExtensions.cs @@ -250,7 +250,7 @@ private static string GetSeparator(this CollectionFormat format) public static string ToString(this IType type, string clientReference, string reference) { PrimaryType primaryType = type as PrimaryType; - if (type == null || primaryType != null && primaryType.Type == KnownPrimaryType.String) + if (type == null || primaryType != null && primaryType.Type == KnownPrimaryType.String && primaryType.KnownFormat != KnownFormat.@char) { return reference; } @@ -303,21 +303,33 @@ public static bool CanBeNull(this IParameter parameter) /// True if the type maps to a C# value type, otherwise false public static bool IsValueType(this IType type) { - PrimaryType primaryType = type as PrimaryType; - EnumType enumType = type as EnumType; - return enumType != null || - (primaryType != null && - (primaryType.Type == KnownPrimaryType.Boolean - || primaryType.Type == KnownPrimaryType.DateTime - || primaryType.Type == KnownPrimaryType.Date - || primaryType.Type == KnownPrimaryType.Decimal - || primaryType.Type == KnownPrimaryType.Double - || primaryType.Type == KnownPrimaryType.Int - || primaryType.Type == KnownPrimaryType.Long - || primaryType.Type == KnownPrimaryType.TimeSpan - || primaryType.Type == KnownPrimaryType.DateTimeRfc1123 - || primaryType.Type == KnownPrimaryType.UnixTime - || primaryType.Type == KnownPrimaryType.Uuid)); + if (type is EnumType) + { + return true; + } + + switch ((type as PrimaryType)?.Type ) + { + case KnownPrimaryType.Boolean: + case KnownPrimaryType.DateTime: + case KnownPrimaryType.Date: + case KnownPrimaryType.Decimal: + case KnownPrimaryType.Double: + case KnownPrimaryType.Int: + case KnownPrimaryType.Long: + case KnownPrimaryType.TimeSpan: + case KnownPrimaryType.DateTimeRfc1123: + case KnownPrimaryType.UnixTime: + case KnownPrimaryType.Uuid: + return true; + + case KnownPrimaryType.String: + return ((PrimaryType) type).KnownFormat == KnownFormat.@char; + + default: + return false; + } + } public static string CheckNull(string valueReference, string executionBlock) @@ -359,7 +371,7 @@ public static string ValidateType(this IType type, IScopeProvider scope, string if (constraints != null && constraints.Any()) { - AppendConstraintValidations(valueReference, constraints, sb); + AppendConstraintValidations(valueReference, constraints, sb, (type as PrimaryType)?.KnownFormat ?? KnownFormat.none); } if (sequence != null && sequence.ShouldValidateChain()) @@ -403,60 +415,49 @@ public static string ValidateType(this IType type, IScopeProvider scope, string } - private static void AppendConstraintValidations(string valueReference, Dictionary constraints, IndentedStringBuilder sb) + private static void AppendConstraintValidations(string valueReference, Dictionary constraints, IndentedStringBuilder sb, KnownFormat format) { foreach (var constraint in constraints.Keys) { string constraintCheck; - string constraintValue = constraints[constraint]; + string constraintValue = (format == KnownFormat.@char) ?$"'{constraints[constraint]}'" : constraints[constraint]; switch (constraint) { case Constraint.ExclusiveMaximum: - constraintCheck = string.Format(CultureInfo.InvariantCulture, "{0} >= {1}", valueReference, - constraints[constraint]); + constraintCheck = $"{valueReference} >= {constraintValue}"; break; case Constraint.ExclusiveMinimum: - constraintCheck = string.Format(CultureInfo.InvariantCulture, "{0} <= {1}", valueReference, - constraints[constraint]); + constraintCheck = $"{valueReference} <= {constraintValue}"; break; case Constraint.InclusiveMaximum: - constraintCheck = string.Format(CultureInfo.InvariantCulture, "{0} > {1}", valueReference, - constraints[constraint]); + constraintCheck = $"{valueReference} > {constraintValue}"; break; case Constraint.InclusiveMinimum: - constraintCheck = string.Format(CultureInfo.InvariantCulture, "{0} < {1}", valueReference, - constraints[constraint]); + constraintCheck = $"{valueReference} < {constraintValue}"; break; case Constraint.MaxItems: - constraintCheck = string.Format(CultureInfo.InvariantCulture, "{0}.Count > {1}", valueReference, - constraints[constraint]); + constraintCheck = $"{valueReference}.Count > {constraintValue}"; break; case Constraint.MaxLength: - constraintCheck = string.Format(CultureInfo.InvariantCulture, "{0}.Length > {1}", valueReference, - constraints[constraint]); + constraintCheck = $"{valueReference}.Length > {constraintValue}"; break; case Constraint.MinItems: - constraintCheck = string.Format(CultureInfo.InvariantCulture, "{0}.Count < {1}", valueReference, - constraints[constraint]); + constraintCheck = $"{valueReference}.Count < {constraintValue}"; break; case Constraint.MinLength: - constraintCheck = string.Format(CultureInfo.InvariantCulture, "{0}.Length < {1}", valueReference, - constraints[constraint]); + constraintCheck = $"{valueReference}.Length < {constraintValue}"; break; case Constraint.MultipleOf: - constraintCheck = string.Format(CultureInfo.InvariantCulture, "{0} % {1} != 0", valueReference, - constraints[constraint]); + constraintCheck = $"{valueReference} % {constraintValue} != 0"; break; case Constraint.Pattern: - constraintValue = "\"" + constraintValue.Replace("\\", "\\\\") + "\""; - constraintCheck = string.Format(CultureInfo.InvariantCulture, - "!System.Text.RegularExpressions.Regex.IsMatch({0}, {1})", valueReference, constraintValue); + constraintValue = $"\"{constraintValue.Replace("\\", "\\\\")}\""; + constraintCheck = $"!System.Text.RegularExpressions.Regex.IsMatch({valueReference}, {constraintValue})"; break; case Constraint.UniqueItems: if ("true".Equals(constraints[constraint], StringComparison.OrdinalIgnoreCase)) { - constraintCheck = string.Format(CultureInfo.InvariantCulture, - "{0}.Count != {0}.Distinct().Count()", valueReference); + constraintCheck = $"{valueReference}.Count != {valueReference}.Distinct().Count()"; } else { diff --git a/src/modeler/AutoRest.Swagger/Model/SwaggerObject.cs b/src/modeler/AutoRest.Swagger/Model/SwaggerObject.cs index cae98fa92e720..d6a9fdce24ef4 100644 --- a/src/modeler/AutoRest.Swagger/Model/SwaggerObject.cs +++ b/src/modeler/AutoRest.Swagger/Model/SwaggerObject.cs @@ -34,6 +34,12 @@ public abstract class SwaggerObject : SwaggerBase /// public virtual string Format { get; set; } + /// + /// Returns the KnownFormat of the Format string (provided it matches a KnownFormat) + /// Otherwise, returns KnownFormat.none + /// + public KnownFormat KnownFormat => KnownFormatExtensions.Parse(Format); + /// /// Describes the type of items in the array. /// @@ -97,56 +103,66 @@ public ObjectBuilder GetBuilder(SwaggerModeler swaggerSpecBuilder) return new ObjectBuilder(this, swaggerSpecBuilder); } + /// + /// Returns the PrimaryType that the SwaggerObject maps to, given the Type and the KnownFormat. + /// + /// Note: Since a given language still may interpret the value of the Format after this, + /// it is possible the final implemented type may not be the type given here. + /// + /// This allows languages to not have a specific PrimaryType decided by the Modeler. + /// + /// For example, if the Type is DataType.String, and the KnownFormat is 'char' the C# generator + /// will end up creating a char type in the generated code, but other languages will still + /// use string. + /// + /// + /// The PrimaryType that best represents this object. + /// public PrimaryType ToType() { switch (Type) { case DataType.String: - if (string.Equals("date", Format, StringComparison.OrdinalIgnoreCase)) + switch (KnownFormat) { + case KnownFormat.date: return new PrimaryType(KnownPrimaryType.Date); - } - if (string.Equals("date-time", Format, StringComparison.OrdinalIgnoreCase)) - { + case KnownFormat.date_time: return new PrimaryType(KnownPrimaryType.DateTime); - } - if (string.Equals("date-time-rfc1123", Format, StringComparison.OrdinalIgnoreCase)) - { + case KnownFormat.date_time_rfc1123: return new PrimaryType(KnownPrimaryType.DateTimeRfc1123); - } - if (string.Equals("byte", Format, StringComparison.OrdinalIgnoreCase)) - { + case KnownFormat.@byte: return new PrimaryType(KnownPrimaryType.ByteArray); - } - if (string.Equals("duration", Format, StringComparison.OrdinalIgnoreCase)) - { + case KnownFormat.duration: return new PrimaryType(KnownPrimaryType.TimeSpan); - } - if (string.Equals("uuid", Format, StringComparison.OrdinalIgnoreCase)) - { + case KnownFormat.uuid: return new PrimaryType(KnownPrimaryType.Uuid); - } - if (string.Equals("base64url", Format, StringComparison.OrdinalIgnoreCase)) - { + case KnownFormat.base64url: return new PrimaryType(KnownPrimaryType.Base64Url); + default: + return new PrimaryType(KnownPrimaryType.String); } - return new PrimaryType(KnownPrimaryType.String); + case DataType.Number: - if (string.Equals("decimal", Format, StringComparison.OrdinalIgnoreCase)) + switch (KnownFormat) { + case KnownFormat.@decimal: return new PrimaryType(KnownPrimaryType.Decimal); + default: + return new PrimaryType(KnownPrimaryType.Double); } - return new PrimaryType(KnownPrimaryType.Double); + case DataType.Integer: - if (string.Equals("int64", Format, StringComparison.OrdinalIgnoreCase)) + switch (KnownFormat) { + case KnownFormat.int64: return new PrimaryType(KnownPrimaryType.Long); - } - if (string.Equals("unixtime", Format, StringComparison.OrdinalIgnoreCase)) - { + case KnownFormat.unixtime: return new PrimaryType(KnownPrimaryType.UnixTime); + default: + return new PrimaryType(KnownPrimaryType.Int); } - return new PrimaryType(KnownPrimaryType.Int); + case DataType.Boolean: return new PrimaryType(KnownPrimaryType.Boolean); case DataType.Object: