From 836c71ff7d2b0822b43ec8c99b8484aa04032ae7 Mon Sep 17 00:00:00 2001 From: Garrett Serack Date: Wed, 1 Jun 2016 13:23:47 -0700 Subject: [PATCH] Support additionalProperties that accept all unbound properties. (see #1057) (#1112) * WIP: additionalProperties * Add support for OpenAPI 'additionalProperties' that act as catch-all properties. * Added description to generated additionalProperties * updated gulpfile for additional properties test --- .../ClientModel/DictionaryType.cs | 7 + .../CSharp.Tests/AdditionalPropertiesTests.cs | 73 ++++++++ .../Expected/Additional.Properties/Get.cs | 177 ++++++++++++++++++ .../Additional.Properties/GetExtensions.cs | 53 ++++++ .../Expected/Additional.Properties/IGet.cs | 35 ++++ .../IPetStoreonHeroku.cs | 57 ++++++ .../Expected/Additional.Properties/IPost.cs | 35 ++++ .../Expected/Additional.Properties/IPut.cs | 35 ++++ .../Additional.Properties/Models/Feature.cs | 45 +++++ .../Additional.Properties/Models/Pet.cs | 71 +++++++ .../Models/WithStringDictionary.cs | 47 +++++ .../Models/WithTypedDictionary.cs | 47 +++++ .../Models/WithUntypedDictionary.cs | 47 +++++ .../Additional.Properties/PetStoreonHeroku.cs | 167 +++++++++++++++++ .../Expected/Additional.Properties/Post.cs | 152 +++++++++++++++ .../Additional.Properties/PostExtensions.cs | 50 +++++ .../Expected/Additional.Properties/Put.cs | 152 +++++++++++++++ .../Additional.Properties/PutExtensions.cs | 50 +++++ .../swagger-additional-properties.yaml | 118 ++++++++++++ .../CSharp/Templates/ModelTemplate.cshtml | 8 + .../AutoRest.Modeler.Swagger.Tests.csproj | 5 +- .../swagger-additional-properties.yaml | 122 ++++++++++++ .../Swagger.Tests/SwaggerModelerTests.cs | 71 +++++++ AutoRest/Modelers/Swagger/SchemaBuilder.cs | 33 +++- gulpfile.js | 1 + 25 files changed, 1654 insertions(+), 4 deletions(-) create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/AdditionalPropertiesTests.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Get.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/GetExtensions.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IGet.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPetStoreonHeroku.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPost.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPut.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/Feature.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/Pet.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithStringDictionary.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithTypedDictionary.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithUntypedDictionary.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PetStoreonHeroku.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Post.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PostExtensions.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Put.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PutExtensions.cs create mode 100644 AutoRest/Generators/CSharp/CSharp.Tests/Swagger/swagger-additional-properties.yaml create mode 100644 AutoRest/Modelers/Swagger.Tests/Swagger/swagger-additional-properties.yaml diff --git a/AutoRest/AutoRest.Core/ClientModel/DictionaryType.cs b/AutoRest/AutoRest.Core/ClientModel/DictionaryType.cs index 34cb3efb3d1a4..6dbb1940a16f9 100644 --- a/AutoRest/AutoRest.Core/ClientModel/DictionaryType.cs +++ b/AutoRest/AutoRest.Core/ClientModel/DictionaryType.cs @@ -25,6 +25,12 @@ public DictionaryType() /// public string NameFormat { get; set; } + /// + /// Indicates that the class should deserialize properties with no matching class member into this collection. + /// + public bool SupportsAdditionalProperties { get; set; } + + /// /// Gets the type name /// @@ -66,5 +72,6 @@ public override int GetHashCode() { return ValueType.GetHashCode(); } + } } \ No newline at end of file diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/AdditionalPropertiesTests.cs b/AutoRest/Generators/CSharp/CSharp.Tests/AdditionalPropertiesTests.cs new file mode 100644 index 0000000000000..51720a8b48381 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/AdditionalPropertiesTests.cs @@ -0,0 +1,73 @@ +// 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 System.Linq; +using System.Net.Http; +using System.Reflection; +using Fixtures.AdditionalProperties; +using Fixtures.AdditionalProperties.Models; + +using Xunit; + +namespace Microsoft.Rest.Generator.CSharp.Tests +{ + [Collection("AdditionalProperties Tests")] + public class AdditionalPropertiesTests + { + [Fact] + public void VerifyWithTypedDictionary() + { + // make sure it's constructable + var withTypedDictionary = new WithTypedDictionary(); + Assert.NotNull(withTypedDictionary); + + // check to see if the JsonExtensionData attribute is on the member. + var property = typeof(WithTypedDictionary).GetProperty("AdditionalProperties"); + Assert.NotNull(property); + Assert.True(property.GetCustomAttributes().Any(each => each.GetType().Name == "JsonExtensionDataAttribute")); + + // check to see that the types of the AdditionalProperties object are correct + var propertyType = property.PropertyType; + Assert.Equal(new Type[] { typeof(string), typeof(Feature) }, propertyType.GenericTypeArguments); + } + + [Fact] + public void VerifyWithUntypedDictionary() + { + // make sure it's constructable + var withUntypedDictionary = new WithUntypedDictionary(); + Assert.NotNull(withUntypedDictionary); + + // check to see if the JsonExtensionData attribute is on the member. + var property = typeof(WithUntypedDictionary).GetProperty("AdditionalProperties"); + Assert.NotNull(property); + Assert.True(property.GetCustomAttributes().Any(each => each.GetType().Name == "JsonExtensionDataAttribute")); + + // check to see that the types of the AdditionalProperties object are correct + var propertyType = property.PropertyType; + Assert.Equal(new Type[] { typeof(string), typeof(Object) }, propertyType.GenericTypeArguments); + } + + [Fact] + public void VerifyWithStringDictionary() + { + // make sure it's constructable + var withStringDictionary = new WithStringDictionary(); + Assert.NotNull(withStringDictionary); + + // check to see if the JsonExtensionData attribute is on the member. + var property = typeof(WithStringDictionary).GetProperty("AdditionalProperties"); + Assert.NotNull(property); + Assert.True(property.GetCustomAttributes().Any(each => each.GetType().Name == "JsonExtensionDataAttribute")); + + // check to see that the types of the AdditionalProperties object are correct + var propertyType = property.PropertyType; + Assert.Equal(new Type[] { typeof(string), typeof(string) }, propertyType.GenericTypeArguments); + } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Get.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Get.cs new file mode 100644 index 0000000000000..9464df049aff0 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Get.cs @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties +{ + using System; + using System.Linq; + using System.Collections.Generic; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Text; + using System.Text.RegularExpressions; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + using Newtonsoft.Json; + using Models; + + /// + /// Get operations. + /// + public partial class Get : IServiceOperations, IGet + { + /// + /// Initializes a new instance of the Get class. + /// + /// + /// Reference to the service client. + /// + public Get(PetStoreonHeroku client) + { + if (client == null) + { + throw new ArgumentNullException("client"); + } + this.Client = client; + } + + /// + /// Gets a reference to the PetStoreonHeroku + /// + public PetStoreonHeroku Client { get; private set; } + + /// + /// number of pets to return + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task>> PetsWithHttpMessagesAsync(int? limit = 11, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (limit > 10000) + { + throw new ValidationException(ValidationRules.InclusiveMaximum, "limit", 10000); + } + if (limit < 11) + { + throw new ValidationException(ValidationRules.InclusiveMinimum, "limit", 11); + } + // Tracing + bool _shouldTrace = ServiceClientTracing.IsEnabled; + string _invocationId = null; + if (_shouldTrace) + { + _invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("limit", limit); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(_invocationId, this, "Pets", tracingParameters); + } + // Construct URL + var _baseUrl = this.Client.BaseUri.AbsoluteUri; + var _url = new Uri(new Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "").ToString(); + List _queryParameters = new List(); + if (limit != null) + { + _queryParameters.Add(string.Format("limit={0}", Uri.EscapeDataString(SafeJsonConvert.SerializeObject(limit, this.Client.SerializationSettings).Trim('"')))); + } + if (_queryParameters.Count > 0) + { + _url += "?" + string.Join("&", _queryParameters); + } + // Create HTTP transport objects + HttpRequestMessage _httpRequest = new HttpRequestMessage(); + HttpResponseMessage _httpResponse = null; + _httpRequest.Method = new HttpMethod("GET"); + _httpRequest.RequestUri = new Uri(_url); + // Set Headers + if (customHeaders != null) + { + foreach(var _header in customHeaders) + { + if (_httpRequest.Headers.Contains(_header.Key)) + { + _httpRequest.Headers.Remove(_header.Key); + } + _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); + } + } + + // Serialize Request + string _requestContent = null; + // Send Request + if (_shouldTrace) + { + ServiceClientTracing.SendRequest(_invocationId, _httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + _httpResponse = await this.Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + if (_shouldTrace) + { + ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); + } + HttpStatusCode _statusCode = _httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string _responseContent = null; + if ((int)_statusCode != 200) + { + var ex = new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); + ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); + if (_shouldTrace) + { + ServiceClientTracing.Error(_invocationId, ex); + } + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw ex; + } + // Create Result + var _result = new HttpOperationResponse>(); + _result.Request = _httpRequest; + _result.Response = _httpResponse; + // Deserialize Response + if ((int)_statusCode == 200) + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + _result.Body = SafeJsonConvert.DeserializeObject>(_responseContent, this.Client.DeserializationSettings); + } + catch (JsonException ex) + { + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw new SerializationException("Unable to deserialize the response.", _responseContent, ex); + } + } + if (_shouldTrace) + { + ServiceClientTracing.Exit(_invocationId, _result); + } + return _result; + } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/GetExtensions.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/GetExtensions.cs new file mode 100644 index 0000000000000..52fdc00137242 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/GetExtensions.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Rest; + using Models; + + /// + /// Extension methods for Get. + /// + public static partial class GetExtensions + { + /// + /// The operations group for this extension method. + /// + /// + /// number of pets to return + /// + public static IList Pets(this IGet operations, int? limit = 11) + { + return Task.Factory.StartNew(s => ((IGet)s).PetsAsync(limit), operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// The operations group for this extension method. + /// + /// + /// number of pets to return + /// + /// + /// The cancellation token. + /// + public static async Task> PetsAsync(this IGet operations, int? limit = 11, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await operations.PetsWithHttpMessagesAsync(limit, null, cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IGet.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IGet.cs new file mode 100644 index 0000000000000..8441b6544b9c1 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IGet.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Rest; + using Models; + + /// + /// Get operations. + /// + public partial interface IGet + { + /// + /// number of pets to return + /// + /// + /// The headers that will be added to request. + /// + /// + /// The cancellation token. + /// + Task>> PetsWithHttpMessagesAsync(int? limit = 11, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPetStoreonHeroku.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPetStoreonHeroku.cs new file mode 100644 index 0000000000000..a556a35a8ab60 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPetStoreonHeroku.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Newtonsoft.Json; + using Microsoft.Rest; + using Models; + + /// + /// **This example has a working backend hosted in Heroku** + /// + public partial interface IPetStoreonHeroku : IDisposable + { + /// + /// The base URI of the service. + /// + Uri BaseUri { get; set; } + + /// + /// Gets or sets json serialization settings. + /// + JsonSerializerSettings SerializationSettings { get; } + + /// + /// Gets or sets json deserialization settings. + /// + JsonSerializerSettings DeserializationSettings { get; } + + + /// + /// Gets the IGet. + /// + IGet Get { get; } + + /// + /// Gets the IPost. + /// + IPost Post { get; } + + /// + /// Gets the IPut. + /// + IPut Put { get; } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPost.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPost.cs new file mode 100644 index 0000000000000..6b5423b4a4c25 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPost.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Rest; + using Models; + + /// + /// Post operations. + /// + public partial interface IPost + { + /// + /// The pet JSON you want to post + /// + /// + /// The headers that will be added to request. + /// + /// + /// The cancellation token. + /// + Task PetsWithHttpMessagesAsync(Pet pet, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPut.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPut.cs new file mode 100644 index 0000000000000..79a1f17c9ec91 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/IPut.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Rest; + using Models; + + /// + /// Put operations. + /// + public partial interface IPut + { + /// + /// The pet JSON you want to post + /// + /// + /// The headers that will be added to request. + /// + /// + /// The cancellation token. + /// + Task PetsWithHttpMessagesAsync(Pet pet, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/Feature.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/Feature.cs new file mode 100644 index 0000000000000..8273f1c0e00cd --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/Feature.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties.Models +{ + using System; + using System.Linq; + using System.Collections.Generic; + using Newtonsoft.Json; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + + public partial class Feature + { + /// + /// Initializes a new instance of the Feature class. + /// + public Feature() { } + + /// + /// Initializes a new instance of the Feature class. + /// + public Feature(string foo = default(string), int? bar = default(int?)) + { + Foo = foo; + Bar = bar; + } + + /// + /// + [JsonProperty(PropertyName = "foo")] + public string Foo { get; set; } + + /// + /// + [JsonProperty(PropertyName = "bar")] + public int? Bar { get; set; } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/Pet.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/Pet.cs new file mode 100644 index 0000000000000..b6fded6e63f23 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/Pet.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties.Models +{ + using System; + using System.Linq; + using System.Collections.Generic; + using Newtonsoft.Json; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + + public partial class Pet + { + /// + /// Initializes a new instance of the Pet class. + /// + public Pet() { } + + /// + /// Initializes a new instance of the Pet class. + /// + public Pet(IDictionary additionalProperties = default(IDictionary), string name = default(string), int? birthday = default(int?), WithStringDictionary wsd = default(WithStringDictionary), WithUntypedDictionary wud = default(WithUntypedDictionary), WithTypedDictionary wtd = default(WithTypedDictionary)) + { + AdditionalProperties = additionalProperties; + Name = name; + Birthday = birthday; + Wsd = wsd; + Wud = wud; + Wtd = wtd; + } + + /// + /// Gets or sets unmatched properties from the message are + /// deserialized this collection + /// + [JsonExtensionData] + public IDictionary AdditionalProperties { get; set; } + + /// + /// + [JsonProperty(PropertyName = "name")] + public string Name { get; set; } + + /// + /// + [JsonProperty(PropertyName = "birthday")] + public int? Birthday { get; set; } + + /// + /// + [JsonProperty(PropertyName = "wsd")] + public WithStringDictionary Wsd { get; set; } + + /// + /// + [JsonProperty(PropertyName = "wud")] + public WithUntypedDictionary Wud { get; set; } + + /// + /// + [JsonProperty(PropertyName = "wtd")] + public WithTypedDictionary Wtd { get; set; } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithStringDictionary.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithStringDictionary.cs new file mode 100644 index 0000000000000..114575124bda8 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithStringDictionary.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties.Models +{ + using System; + using System.Linq; + using System.Collections.Generic; + using Newtonsoft.Json; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + + public partial class WithStringDictionary + { + /// + /// Initializes a new instance of the WithStringDictionary class. + /// + public WithStringDictionary() { } + + /// + /// Initializes a new instance of the WithStringDictionary class. + /// + public WithStringDictionary(IDictionary additionalProperties = default(IDictionary), string abc = default(string)) + { + AdditionalProperties = additionalProperties; + Abc = abc; + } + + /// + /// Gets or sets unmatched properties from the message are + /// deserialized this collection + /// + [JsonExtensionData] + public IDictionary AdditionalProperties { get; set; } + + /// + /// + [JsonProperty(PropertyName = "abc")] + public string Abc { get; set; } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithTypedDictionary.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithTypedDictionary.cs new file mode 100644 index 0000000000000..91be0e718f83d --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithTypedDictionary.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties.Models +{ + using System; + using System.Linq; + using System.Collections.Generic; + using Newtonsoft.Json; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + + public partial class WithTypedDictionary + { + /// + /// Initializes a new instance of the WithTypedDictionary class. + /// + public WithTypedDictionary() { } + + /// + /// Initializes a new instance of the WithTypedDictionary class. + /// + public WithTypedDictionary(IDictionary additionalProperties = default(IDictionary), string abc = default(string)) + { + AdditionalProperties = additionalProperties; + Abc = abc; + } + + /// + /// Gets or sets unmatched properties from the message are + /// deserialized this collection + /// + [JsonExtensionData] + public IDictionary AdditionalProperties { get; set; } + + /// + /// + [JsonProperty(PropertyName = "abc")] + public string Abc { get; set; } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithUntypedDictionary.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithUntypedDictionary.cs new file mode 100644 index 0000000000000..d6cd540ef251b --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Models/WithUntypedDictionary.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties.Models +{ + using System; + using System.Linq; + using System.Collections.Generic; + using Newtonsoft.Json; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + + public partial class WithUntypedDictionary + { + /// + /// Initializes a new instance of the WithUntypedDictionary class. + /// + public WithUntypedDictionary() { } + + /// + /// Initializes a new instance of the WithUntypedDictionary class. + /// + public WithUntypedDictionary(IDictionary additionalProperties = default(IDictionary), string abc = default(string)) + { + AdditionalProperties = additionalProperties; + Abc = abc; + } + + /// + /// Gets or sets unmatched properties from the message are + /// deserialized this collection + /// + [JsonExtensionData] + public IDictionary AdditionalProperties { get; set; } + + /// + /// + [JsonProperty(PropertyName = "abc")] + public string Abc { get; set; } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PetStoreonHeroku.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PetStoreonHeroku.cs new file mode 100644 index 0000000000000..ab5924a4b2821 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PetStoreonHeroku.cs @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties +{ + using System; + using System.Linq; + using System.Collections.Generic; + using System.Diagnostics; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Text; + using System.Text.RegularExpressions; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + using Newtonsoft.Json; + using Models; + + /// + /// **This example has a working backend hosted in Heroku** + /// + public partial class PetStoreonHeroku : ServiceClient, IPetStoreonHeroku + { + /// + /// The base URI of the service. + /// + public Uri BaseUri { get; set; } + + /// + /// Gets or sets json serialization settings. + /// + public JsonSerializerSettings SerializationSettings { get; private set; } + + /// + /// Gets or sets json deserialization settings. + /// + public JsonSerializerSettings DeserializationSettings { get; private set; } + + /// + /// Gets the IGet. + /// + public virtual IGet Get { get; private set; } + + /// + /// Gets the IPost. + /// + public virtual IPost Post { get; private set; } + + /// + /// Gets the IPut. + /// + public virtual IPut Put { get; private set; } + + /// + /// Initializes a new instance of the PetStoreonHeroku class. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + public PetStoreonHeroku(params DelegatingHandler[] handlers) : base(handlers) + { + this.Initialize(); + } + + /// + /// Initializes a new instance of the PetStoreonHeroku class. + /// + /// + /// Optional. The http client handler used to handle http transport. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + public PetStoreonHeroku(HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : base(rootHandler, handlers) + { + this.Initialize(); + } + + /// + /// Initializes a new instance of the PetStoreonHeroku class. + /// + /// + /// Optional. The base URI of the service. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + public PetStoreonHeroku(Uri baseUri, params DelegatingHandler[] handlers) : this(handlers) + { + if (baseUri == null) + { + throw new ArgumentNullException("baseUri"); + } + this.BaseUri = baseUri; + } + + /// + /// Initializes a new instance of the PetStoreonHeroku class. + /// + /// + /// Optional. The base URI of the service. + /// + /// + /// Optional. The http client handler used to handle http transport. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + public PetStoreonHeroku(Uri baseUri, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : this(rootHandler, handlers) + { + if (baseUri == null) + { + throw new ArgumentNullException("baseUri"); + } + this.BaseUri = baseUri; + } + + /// + /// An optional partial-method to perform custom initialization. + /// + partial void CustomInitialize(); + /// + /// Initializes client properties. + /// + private void Initialize() + { + this.Get = new Get(this); + this.Post = new Post(this); + this.Put = new Put(this); + this.BaseUri = new Uri("http://petstore-api.herokuapp.com/my/pet"); + SerializationSettings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + DateFormatHandling = DateFormatHandling.IsoDateFormat, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver(), + Converters = new List + { + new Iso8601TimeSpanConverter() + } + }; + DeserializationSettings = new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.IsoDateFormat, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver(), + Converters = new List + { + new Iso8601TimeSpanConverter() + } + }; + CustomInitialize(); + } + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Post.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Post.cs new file mode 100644 index 0000000000000..301d47eb8289b --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Post.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties +{ + using System; + using System.Linq; + using System.Collections.Generic; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Text; + using System.Text.RegularExpressions; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + using Newtonsoft.Json; + using Models; + + /// + /// Post operations. + /// + public partial class Post : IServiceOperations, IPost + { + /// + /// Initializes a new instance of the Post class. + /// + /// + /// Reference to the service client. + /// + public Post(PetStoreonHeroku client) + { + if (client == null) + { + throw new ArgumentNullException("client"); + } + this.Client = client; + } + + /// + /// Gets a reference to the PetStoreonHeroku + /// + public PetStoreonHeroku Client { get; private set; } + + /// + /// The pet JSON you want to post + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task PetsWithHttpMessagesAsync(Pet pet, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (pet == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "pet"); + } + // Tracing + bool _shouldTrace = ServiceClientTracing.IsEnabled; + string _invocationId = null; + if (_shouldTrace) + { + _invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("pet", pet); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(_invocationId, this, "Pets", tracingParameters); + } + // Construct URL + var _baseUrl = this.Client.BaseUri.AbsoluteUri; + var _url = new Uri(new Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "").ToString(); + // Create HTTP transport objects + HttpRequestMessage _httpRequest = new HttpRequestMessage(); + HttpResponseMessage _httpResponse = null; + _httpRequest.Method = new HttpMethod("POST"); + _httpRequest.RequestUri = new Uri(_url); + // Set Headers + if (customHeaders != null) + { + foreach(var _header in customHeaders) + { + if (_httpRequest.Headers.Contains(_header.Key)) + { + _httpRequest.Headers.Remove(_header.Key); + } + _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); + } + } + + // Serialize Request + string _requestContent = null; + if(pet != null) + { + _requestContent = SafeJsonConvert.SerializeObject(pet, this.Client.SerializationSettings); + _httpRequest.Content = new StringContent(_requestContent, Encoding.UTF8); + _httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); + } + // Send Request + if (_shouldTrace) + { + ServiceClientTracing.SendRequest(_invocationId, _httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + _httpResponse = await this.Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + if (_shouldTrace) + { + ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); + } + HttpStatusCode _statusCode = _httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string _responseContent = null; + if ((int)_statusCode != 200) + { + var ex = new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); + ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); + if (_shouldTrace) + { + ServiceClientTracing.Error(_invocationId, ex); + } + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw ex; + } + // Create Result + var _result = new HttpOperationResponse(); + _result.Request = _httpRequest; + _result.Response = _httpResponse; + if (_shouldTrace) + { + ServiceClientTracing.Exit(_invocationId, _result); + } + return _result; + } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PostExtensions.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PostExtensions.cs new file mode 100644 index 0000000000000..3d59a1fa81657 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PostExtensions.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Rest; + using Models; + + /// + /// Extension methods for Post. + /// + public static partial class PostExtensions + { + /// + /// The operations group for this extension method. + /// + /// + /// The pet JSON you want to post + /// + public static void Pets(this IPost operations, Pet pet) + { + Task.Factory.StartNew(s => ((IPost)s).PetsAsync(pet), operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// The operations group for this extension method. + /// + /// + /// The pet JSON you want to post + /// + /// + /// The cancellation token. + /// + public static async Task PetsAsync(this IPost operations, Pet pet, CancellationToken cancellationToken = default(CancellationToken)) + { + await operations.PetsWithHttpMessagesAsync(pet, null, cancellationToken).ConfigureAwait(false); + } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Put.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Put.cs new file mode 100644 index 0000000000000..9282fba3131a8 --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/Put.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties +{ + using System; + using System.Linq; + using System.Collections.Generic; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Text; + using System.Text.RegularExpressions; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + using Newtonsoft.Json; + using Models; + + /// + /// Put operations. + /// + public partial class Put : IServiceOperations, IPut + { + /// + /// Initializes a new instance of the Put class. + /// + /// + /// Reference to the service client. + /// + public Put(PetStoreonHeroku client) + { + if (client == null) + { + throw new ArgumentNullException("client"); + } + this.Client = client; + } + + /// + /// Gets a reference to the PetStoreonHeroku + /// + public PetStoreonHeroku Client { get; private set; } + + /// + /// The pet JSON you want to post + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task PetsWithHttpMessagesAsync(Pet pet, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (pet == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "pet"); + } + // Tracing + bool _shouldTrace = ServiceClientTracing.IsEnabled; + string _invocationId = null; + if (_shouldTrace) + { + _invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("pet", pet); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(_invocationId, this, "Pets", tracingParameters); + } + // Construct URL + var _baseUrl = this.Client.BaseUri.AbsoluteUri; + var _url = new Uri(new Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "").ToString(); + // Create HTTP transport objects + HttpRequestMessage _httpRequest = new HttpRequestMessage(); + HttpResponseMessage _httpResponse = null; + _httpRequest.Method = new HttpMethod("PUT"); + _httpRequest.RequestUri = new Uri(_url); + // Set Headers + if (customHeaders != null) + { + foreach(var _header in customHeaders) + { + if (_httpRequest.Headers.Contains(_header.Key)) + { + _httpRequest.Headers.Remove(_header.Key); + } + _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); + } + } + + // Serialize Request + string _requestContent = null; + if(pet != null) + { + _requestContent = SafeJsonConvert.SerializeObject(pet, this.Client.SerializationSettings); + _httpRequest.Content = new StringContent(_requestContent, Encoding.UTF8); + _httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); + } + // Send Request + if (_shouldTrace) + { + ServiceClientTracing.SendRequest(_invocationId, _httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + _httpResponse = await this.Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + if (_shouldTrace) + { + ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); + } + HttpStatusCode _statusCode = _httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string _responseContent = null; + if ((int)_statusCode != 200) + { + var ex = new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); + ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); + if (_shouldTrace) + { + ServiceClientTracing.Error(_invocationId, ex); + } + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw ex; + } + // Create Result + var _result = new HttpOperationResponse(); + _result.Request = _httpRequest; + _result.Response = _httpResponse; + if (_shouldTrace) + { + ServiceClientTracing.Exit(_invocationId, _result); + } + return _result; + } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PutExtensions.cs b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PutExtensions.cs new file mode 100644 index 0000000000000..ed32f878638cb --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Expected/Additional.Properties/PutExtensions.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Fixtures.AdditionalProperties +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Rest; + using Models; + + /// + /// Extension methods for Put. + /// + public static partial class PutExtensions + { + /// + /// The operations group for this extension method. + /// + /// + /// The pet JSON you want to post + /// + public static void Pets(this IPut operations, Pet pet) + { + Task.Factory.StartNew(s => ((IPut)s).PetsAsync(pet), operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// The operations group for this extension method. + /// + /// + /// The pet JSON you want to post + /// + /// + /// The cancellation token. + /// + public static async Task PetsAsync(this IPut operations, Pet pet, CancellationToken cancellationToken = default(CancellationToken)) + { + await operations.PetsWithHttpMessagesAsync(pet, null, cancellationToken).ConfigureAwait(false); + } + + } +} diff --git a/AutoRest/Generators/CSharp/CSharp.Tests/Swagger/swagger-additional-properties.yaml b/AutoRest/Generators/CSharp/CSharp.Tests/Swagger/swagger-additional-properties.yaml new file mode 100644 index 0000000000000..a2a2aa44e0e4d --- /dev/null +++ b/AutoRest/Generators/CSharp/CSharp.Tests/Swagger/swagger-additional-properties.yaml @@ -0,0 +1,118 @@ +swagger: '2.0' +info: + version: 1.0.0 + title: PetStore on Heroku + description: | + **This example has a working backend hosted in Heroku** +host: petstore-api.herokuapp.com +basePath: /my/pet +schemes: + - http + - https +consumes: + - application/json + - text/xml +produces: + - application/json + - text/html +paths: + /: + get: + operationId: get_pets + parameters: + - name: limit + in: query + description: number of pets to return + type: integer + default: 11 + minimum: 11 + maximum: 10000 + responses: + 200: + description: List all pets + schema: + title: Pets + type: array + items: + $ref: '#/definitions/Pet' + post: + operationId: post_pets + parameters: + - name: pet + in: body + description: The pet JSON you want to post + schema: + $ref: '#/definitions/Pet' + required: true + responses: + 200: + description: Make a new pet + put: + operationId: put_pets + parameters: + - name: pet + in: body + description: The pet JSON you want to post + schema: + $ref: '#/definitions/Pet' + required: true + responses: + 200: + description: Updates the pet + +definitions: + Pet: + type: object + additionalProperties: + type: object + $ref: '#/definitions/Feature' + + properties: + name: + type: string + birthday: + type: integer + format: int32 + wsd: + type: object + $ref: '#/definitions/WithStringDictionary' + wud: + type: object + $ref: '#/definitions/WithUntypedDictionary' + wtd: + type: object + $ref: '#/definitions/WithTypedDictionary' + + Feature: + type: object + properties: + foo: + type: string + bar: + type: integer + format: int32 + + WithStringDictionary: + type: object + properties: + abc: + type: string + additionalProperties: + type: string + + WithUntypedDictionary: + type: object + properties: + abc: + type: string + additionalProperties: + type: object + + WithTypedDictionary: + type: object + properties: + abc: + type: string + additionalProperties: + type: object + $ref: '#/definitions/Feature' diff --git a/AutoRest/Generators/CSharp/CSharp/Templates/ModelTemplate.cshtml b/AutoRest/Generators/CSharp/CSharp/Templates/ModelTemplate.cshtml index aeb27d92a4579..c9fa91236e944 100644 --- a/AutoRest/Generators/CSharp/CSharp/Templates/ModelTemplate.cshtml +++ b/AutoRest/Generators/CSharp/CSharp/Templates/ModelTemplate.cshtml @@ -127,7 +127,15 @@ namespace @(Settings.Namespace).Models { @:[JsonConverter(typeof(UnixTimeJsonConverter))] } + + if (property.Type is DictionaryType && (property.Type as DictionaryType).SupportsAdditionalProperties) + { + @:[JsonExtensionData] + } + else + { @:[JsonProperty(PropertyName = "@property.SerializedName")] + } @:public @property.Type.Name @property.Name { get; @(property.IsReadOnly ? "private " : "")set; } @EmptyLine } diff --git a/AutoRest/Modelers/Swagger.Tests/AutoRest.Modeler.Swagger.Tests.csproj b/AutoRest/Modelers/Swagger.Tests/AutoRest.Modeler.Swagger.Tests.csproj index 9984e9023b635..2db5e127e7a87 100644 --- a/AutoRest/Modelers/Swagger.Tests/AutoRest.Modeler.Swagger.Tests.csproj +++ b/AutoRest/Modelers/Swagger.Tests/AutoRest.Modeler.Swagger.Tests.csproj @@ -84,6 +84,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -152,7 +155,7 @@ - + diff --git a/AutoRest/Modelers/Swagger.Tests/Swagger/swagger-additional-properties.yaml b/AutoRest/Modelers/Swagger.Tests/Swagger/swagger-additional-properties.yaml new file mode 100644 index 0000000000000..7a93d788e1d27 --- /dev/null +++ b/AutoRest/Modelers/Swagger.Tests/Swagger/swagger-additional-properties.yaml @@ -0,0 +1,122 @@ +swagger: '2.0' +info: + version: 1.0.0 + title: PetStore on Heroku + description: | + **This example has a working backend hosted in Heroku** + + You can try all HTTP operation described in this Swagger spec. + + Find source code of this API [here](https://github.com/mohsen1/petstore-api) +host: petstore-api.herokuapp.com +basePath: /pet +schemes: + - http + - https +consumes: + - application/json + - text/xml +produces: + - application/json + - text/html +paths: + /: + get: + operationId: get_pets + parameters: + - name: limit + in: query + description: number of pets to return + type: integer + default: 11 + minimum: 11 + maximum: 10000 + responses: + 200: + description: List all pets + schema: + title: Pets + type: array + items: + $ref: '#/definitions/Pet' + post: + operationId: post_pets + parameters: + - name: pet + in: body + description: The pet JSON you want to post + schema: + $ref: '#/definitions/Pet' + required: true + responses: + 200: + description: Make a new pet + put: + operationId: put_pets + parameters: + - name: pet + in: body + description: The pet JSON you want to post + schema: + $ref: '#/definitions/Pet' + required: true + responses: + 200: + description: Updates the pet + +definitions: + Pet: + type: object + additionalProperties: + type: object + $ref: '#/definitions/Feature' + + properties: + name: + type: string + birthday: + type: integer + format: int32 + wsd: + type: object + $ref: '#/definitions/WithStringDictionary' + wud: + type: object + $ref: '#/definitions/WithUntypedDictionary' + wtd: + type: object + $ref: '#/definitions/WithTypedDictionary' + + Feature: + type: object + properties: + foo: + type: string + bar: + type: integer + format: int32 + + WithStringDictionary: + type: object + properties: + abc: + type: string + additionalProperties: + type: string + + WithUntypedDictionary: + properties: + abc: + type: string + type: object + additionalProperties: + type: object + + WithTypedDictionary: + type: object + properties: + abc: + type: string + additionalProperties: + type: object + $ref: '#/definitions/Feature' diff --git a/AutoRest/Modelers/Swagger.Tests/SwaggerModelerTests.cs b/AutoRest/Modelers/Swagger.Tests/SwaggerModelerTests.cs index 062e232c519b3..f3ffac7578c51 100644 --- a/AutoRest/Modelers/Swagger.Tests/SwaggerModelerTests.cs +++ b/AutoRest/Modelers/Swagger.Tests/SwaggerModelerTests.cs @@ -634,5 +634,76 @@ public void TestYamlParsing() Assert.NotNull(clientModel); } + + [Fact] + public void TestAdditionalProperties() + { + Generator.Modeler modeler = new SwaggerModeler(new Settings + { + Namespace = "Test", + Input = Path.Combine("Swagger", "swagger-additional-properties.yaml") + }); + var clientModel = modeler.Build(); + + Assert.NotNull(clientModel); + Assert.Equal(5, clientModel.ModelTypes.Count); + + // did we find the type? + var wtd = clientModel.ModelTypes.FirstOrDefault(each => each.Name == "WithTypedDictionary"); + Assert.NotNull(wtd); + + // did we find the member called 'additionalProperties' + var prop = wtd.Properties.FirstOrDefault(each => each.Name == "additionalProperties"); + Assert.NotNull(prop); + + // is it a DictionaryType? + var dictionaryProperty = prop.Type as DictionaryType; + Assert.NotNull(dictionaryProperty); + + // is a string,string dictionary? + Assert.Equal("IDictionary", dictionaryProperty.Name); + Assert.Equal("Feature", dictionaryProperty.ValueType.Name); + + // is it marked as an 'additionalProperties' bucket? + Assert.True(dictionaryProperty.SupportsAdditionalProperties); + + // did we find the type? + var wud = clientModel.ModelTypes.FirstOrDefault(each => each.Name == "WithUntypedDictionary"); + Assert.NotNull(wud); + + // did we find the member called 'additionalProperties' + prop = wud.Properties.FirstOrDefault(each => each.Name == "additionalProperties"); + Assert.NotNull(prop); + + // is it a DictionaryType? + dictionaryProperty = prop.Type as DictionaryType; + Assert.NotNull(dictionaryProperty); + + // is a string,string dictionary? + Assert.Equal("IDictionary", dictionaryProperty.Name); + Assert.Equal("Object", dictionaryProperty.ValueType.Name); + + // is it marked as an 'additionalProperties' bucket? + Assert.True(dictionaryProperty.SupportsAdditionalProperties); + + var wsd = clientModel.ModelTypes.FirstOrDefault(each => each.Name == "WithStringDictionary"); + Assert.NotNull(wsd); + + // did we find the member called 'additionalProperties' + prop = wsd.Properties.FirstOrDefault(each => each.Name == "additionalProperties"); + Assert.NotNull(prop); + + // is it a DictionaryType? + dictionaryProperty = prop.Type as DictionaryType; + Assert.NotNull(dictionaryProperty); + + // is a string,string dictionary? + Assert.Equal("IDictionary", dictionaryProperty.Name); + Assert.Equal("String", dictionaryProperty.ValueType.Name); + + // is it marked as an 'additionalProperties' bucket? + Assert.True(dictionaryProperty.SupportsAdditionalProperties); + + } } } diff --git a/AutoRest/Modelers/Swagger/SchemaBuilder.cs b/AutoRest/Modelers/Swagger/SchemaBuilder.cs index 3a92c23c5e467..545c390422e1e 100644 --- a/AutoRest/Modelers/Swagger/SchemaBuilder.cs +++ b/AutoRest/Modelers/Swagger/SchemaBuilder.cs @@ -38,9 +38,12 @@ public override IType BuildServiceType(string serviceTypeName) _schema = Modeler.Resolver.Unwrap(_schema); // If primitive type - if ((_schema.Type != null && _schema.Type != DataType.Object) || - _schema.AdditionalProperties != null) + if (_schema.Type != null && _schema.Type != DataType.Object || ( _schema.AdditionalProperties != null && _schema.Properties.IsNullOrEmpty() )) { + // Notes: + // 'additionalProperties' on a type AND no defined 'properties', indicates that + // this type is a Dictionary. (and is handled by ObjectBuilder) + return _schema.GetBuilder(Modeler).ParentBuildServiceType(serviceTypeName); } @@ -53,7 +56,7 @@ public override IType BuildServiceType(string serviceTypeName) } // If the object does not have any properties, treat it as raw json (i.e. object) - if (_schema.Properties.IsNullOrEmpty() && string.IsNullOrEmpty(_schema.Extends)) + if (_schema.Properties.IsNullOrEmpty() && string.IsNullOrEmpty(_schema.Extends) && _schema.AdditionalProperties == null) { return new PrimaryType(KnownPrimaryType.Object); } @@ -65,9 +68,33 @@ public override IType BuildServiceType(string serviceTypeName) SerializedName = serviceTypeName, Documentation = _schema.Description }; + // Put this in already generated types serializationProperty Modeler.GeneratedTypes[serviceTypeName] = objectType; + if (_schema.Type == DataType.Object && _schema.AdditionalProperties != null) + { + // this schema is defining 'additionalProperties' which expects to create an extra + // property that will catch all the unbound properties during deserialization. + var name = "additionalProperties"; + var propertyType = new DictionaryType + { + ValueType = _schema.AdditionalProperties.GetBuilder(Modeler).BuildServiceType( + _schema.AdditionalProperties.Reference != null + ? _schema.AdditionalProperties.Reference.StripDefinitionPath() + : serviceTypeName + "Value"), + SupportsAdditionalProperties = true + }; + + // now add the extra property to the type. + objectType.Properties.Add(new Property + { + Name = name, + Type = propertyType, + Documentation = "Unmatched properties from the message are deserialized this collection" + }); + } + if (_schema.Properties != null) { // Visit each property and recursively build service types diff --git a/gulpfile.js b/gulpfile.js index 22cc61f881019..13dd7589b0b79 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -319,6 +319,7 @@ gulp.task('regenerate:expected:cs', ['regenerate:expected:cswithcreds', 'regener 'Mirror.Sequences': 'Swagger/swagger-mirror-sequences.json', 'Mirror.Polymorphic': 'Swagger/swagger-mirror-polymorphic.json', 'Internal.Ctors': 'Swagger/swagger-internal-ctors.json', + 'Additional.Properties': 'Swagger/swagger-additional-properties.yaml', 'DateTimeOffset': 'Swagger/swagger-datetimeoffset.json' }, defaultMappings);