diff --git a/src/vanilla/ClientModelExtensions.cs b/src/vanilla/ClientModelExtensions.cs index f23645aa0b2..5a95da0793c 100644 --- a/src/vanilla/ClientModelExtensions.cs +++ b/src/vanilla/ClientModelExtensions.cs @@ -317,9 +317,10 @@ public static string CheckNull(string valueReference, string executionBlock) /// The type to validate /// A scope provider for generating variable names as necessary /// A reference to the value being validated + /// Indicates whether the parameter or property is expressed as a nullable type or not /// Constraints /// The code to validate the reference of the given type - public static string ValidateType(this IModelType type, IChild scope, string valueReference, + public static string ValidateType(this IModelType type, IChild scope, string valueReference, bool isNullable, Dictionary constraints) { if (scope == null) @@ -346,7 +347,7 @@ public static string ValidateType(this IModelType type, IChild scope, string val if (sequence != null && sequence.ShouldValidateChain()) { var elementVar = scope.GetUniqueName("element"); - var innerValidation = sequence.ElementType.ValidateType(scope, elementVar, null); + var innerValidation = sequence.ElementType.ValidateType(scope, elementVar, false, null); if (!string.IsNullOrEmpty(innerValidation)) { sb.AppendLine("foreach (var {0} in {1})", elementVar, valueReference) @@ -358,7 +359,7 @@ public static string ValidateType(this IModelType type, IChild scope, string val else if (dictionary != null && dictionary.ShouldValidateChain()) { var valueVar = scope.GetUniqueName("valueElement"); - var innerValidation = dictionary.ValueType.ValidateType(scope, valueVar, null); + var innerValidation = dictionary.ValueType.ValidateType(scope, valueVar, false, null); if (!string.IsNullOrEmpty(innerValidation)) { sb.AppendLine("foreach (var {0} in {1}.Values)", valueVar, valueReference) @@ -370,7 +371,7 @@ public static string ValidateType(this IModelType type, IChild scope, string val if (sb.ToString().Trim().Length > 0) { - if (type.IsValueType()) + if (type.IsValueType() && !isNullable) { return sb.ToString(); } diff --git a/src/vanilla/Templates/Rest/Client/MethodBodyTemplateRestCall.cshtml b/src/vanilla/Templates/Rest/Client/MethodBodyTemplateRestCall.cshtml index d85576fb636..44de231c677 100644 --- a/src/vanilla/Templates/Rest/Client/MethodBodyTemplateRestCall.cshtml +++ b/src/vanilla/Templates/Rest/Client/MethodBodyTemplateRestCall.cshtml @@ -20,7 +20,7 @@ } if(parameter.CanBeValidated && (Model.HttpMethod != HttpMethod.Patch || parameter.Location != ParameterLocation.Body)) { -@:@(parameter.ModelType.ValidateType(Model, parameter.Name, parameter.Constraints)) +@:@(parameter.ModelType.ValidateType(Model, parameter.Name, parameter.IsNullable(), parameter.Constraints)) } } diff --git a/src/vanilla/Templates/Rest/Common/ModelTemplate.cshtml b/src/vanilla/Templates/Rest/Common/ModelTemplate.cshtml index 04bba639f81..597fc287a33 100644 --- a/src/vanilla/Templates/Rest/Common/ModelTemplate.cshtml +++ b/src/vanilla/Templates/Rest/Common/ModelTemplate.cshtml @@ -205,7 +205,7 @@ foreach (PropertyCs property in Model.InstanceProperties.Where(p => p.IsRequired foreach (var property in Model.InstanceProperties.Where(p => p.Constraints.Any() || !(p.ModelType is PrimaryType))) { anythingToValidate = true; - @:@property.ModelType.ValidateType(Model, $"this.{property.Name}", property.Constraints) + @:@property.ModelType.ValidateType(Model, $"this.{property.Name}", property.IsNullable(), property.Constraints) } if (!anythingToValidate) { diff --git a/src/vanilla/Templates/Rest/Server/ServiceMethodTemplate.cshtml b/src/vanilla/Templates/Rest/Server/ServiceMethodTemplate.cshtml index 785d8f9ced6..fcc7494d338 100644 --- a/src/vanilla/Templates/Rest/Server/ServiceMethodTemplate.cshtml +++ b/src/vanilla/Templates/Rest/Server/ServiceMethodTemplate.cshtml @@ -54,7 +54,7 @@ } if (parameter.CanBeValidated && (Model.HttpMethod != HttpMethod.Patch || parameter.Location != ParameterLocation.Body)) { - @:@(parameter.ModelType.ValidateType(Model, parameter.Name, parameter.Constraints)) + @:@(parameter.ModelType.ValidateType(Model, parameter.Name, parameter.IsNullable(), parameter.Constraints)) } } @if (Model.HttpMethod == HttpMethod.Get) diff --git a/test/Resource/Bug885/Bug885.json b/test/Resource/Bug885/Bug885.json new file mode 100644 index 00000000000..fee6d2ece7e --- /dev/null +++ b/test/Resource/Bug885/Bug885.json @@ -0,0 +1,40 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Simple API", + "termsOfService": "http://helloreverb.com/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host": "test.swagger.io", + "basePath": "/v2", + "schemes": [ + "http" + ], + "paths": {}, + "definitions": { + "DateAfterModification": { + "properties": { + "daysAfterModificationGreaterThan": { + "type": "number", + "multipleOf": 1.0, + "minimum": 0, + "description": "Value indicating the age in days after last modification" + }, + "daysAfterLastAccessTimeGreaterThan": { + "type": "number", + "multipleOf": 1.0, + "minimum": 0, + "description": "Value indicating the age in days after last blob access. This property can only be used in conjuction with last access time tracking policy" + } + }, + "description": "Object to define the number of days after object last modification Or last access. Properties daysAfterModificationGreaterThan and daysAfterLastAccessTimeGreaterThan are mutually exclusive" + } + } +} \ No newline at end of file diff --git a/test/Resource/Bug885/ISimpleAPI.cs b/test/Resource/Bug885/ISimpleAPI.cs new file mode 100644 index 00000000000..1a90d9cf090 --- /dev/null +++ b/test/Resource/Bug885/ISimpleAPI.cs @@ -0,0 +1,33 @@ +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Bug885 +{ + using Models; + using Newtonsoft.Json; + + /// + /// + public partial interface ISimpleAPI : System.IDisposable + { + /// + /// The base URI of the service. + /// + System.Uri BaseUri { get; set; } + + /// + /// Gets or sets json serialization settings. + /// + JsonSerializerSettings SerializationSettings { get; } + + /// + /// Gets or sets json deserialization settings. + /// + JsonSerializerSettings DeserializationSettings { get; } + + + } +} diff --git a/test/Resource/Bug885/Models/DateAfterModification.cs b/test/Resource/Bug885/Models/DateAfterModification.cs new file mode 100644 index 00000000000..c70f4f19a34 --- /dev/null +++ b/test/Resource/Bug885/Models/DateAfterModification.cs @@ -0,0 +1,95 @@ +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Bug885.Models +{ + using Microsoft.Rest; + using Newtonsoft.Json; + using System.Linq; + + /// + /// Object to define the number of days after object last modification Or + /// last access. Properties daysAfterModificationGreaterThan and + /// daysAfterLastAccessTimeGreaterThan are mutually exclusive + /// + public partial class DateAfterModification + { + /// + /// Initializes a new instance of the DateAfterModification class. + /// + public DateAfterModification() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the DateAfterModification class. + /// + /// Value indicating the + /// age in days after last modification + /// Value indicating + /// the age in days after last blob access. This property can only be + /// used in conjuction with last access time tracking policy + public DateAfterModification(double? daysAfterModificationGreaterThan = default(double?), double? daysAfterLastAccessTimeGreaterThan = default(double?)) + { + DaysAfterModificationGreaterThan = daysAfterModificationGreaterThan; + DaysAfterLastAccessTimeGreaterThan = daysAfterLastAccessTimeGreaterThan; + CustomInit(); + } + + /// + /// An initialization method that performs custom operations like setting defaults + /// + partial void CustomInit(); + + /// + /// Gets or sets value indicating the age in days after last + /// modification + /// + [JsonProperty(PropertyName = "daysAfterModificationGreaterThan")] + public double? DaysAfterModificationGreaterThan { get; set; } + + /// + /// Gets or sets value indicating the age in days after last blob + /// access. This property can only be used in conjuction with last + /// access time tracking policy + /// + [JsonProperty(PropertyName = "daysAfterLastAccessTimeGreaterThan")] + public double? DaysAfterLastAccessTimeGreaterThan { get; set; } + + /// + /// Validate the object. + /// + /// + /// Thrown if validation fails + /// + public virtual void Validate() + { + if (DaysAfterModificationGreaterThan != null) + { + if (DaysAfterModificationGreaterThan < 0) + { + throw new ValidationException(ValidationRules.InclusiveMinimum, "DaysAfterModificationGreaterThan", 0); + } + if (DaysAfterModificationGreaterThan % 1 != 0) + { + throw new ValidationException(ValidationRules.MultipleOf, "DaysAfterModificationGreaterThan", 1); + } + } + if (DaysAfterLastAccessTimeGreaterThan != null) + { + if (DaysAfterLastAccessTimeGreaterThan < 0) + { + throw new ValidationException(ValidationRules.InclusiveMinimum, "DaysAfterLastAccessTimeGreaterThan", 0); + } + if (DaysAfterLastAccessTimeGreaterThan % 1 != 0) + { + throw new ValidationException(ValidationRules.MultipleOf, "DaysAfterLastAccessTimeGreaterThan", 1); + } + } + } + } +} diff --git a/test/Resource/Bug885/SimpleAPI.cs b/test/Resource/Bug885/SimpleAPI.cs new file mode 100644 index 00000000000..a41b6176151 --- /dev/null +++ b/test/Resource/Bug885/SimpleAPI.cs @@ -0,0 +1,156 @@ +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Bug885 +{ + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + using Models; + using Newtonsoft.Json; + using System.Collections; + using System.Collections.Generic; + using System.Net; + using System.Net.Http; + + public partial class SimpleAPI : ServiceClient, ISimpleAPI + { + /// + /// The base URI of the service. + /// + public System.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; } + + /// + /// Initializes a new instance of the SimpleAPI class. + /// + /// + /// HttpClient to be used + /// + /// + /// True: will dispose the provided httpClient on calling SimpleAPI.Dispose(). False: will not dispose provided httpClient + public SimpleAPI(HttpClient httpClient, bool disposeHttpClient) : base(httpClient, disposeHttpClient) + { + Initialize(); + } + + /// + /// Initializes a new instance of the SimpleAPI class. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + public SimpleAPI(params DelegatingHandler[] handlers) : base(handlers) + { + Initialize(); + } + + /// + /// Initializes a new instance of the SimpleAPI class. + /// + /// + /// Optional. The http client handler used to handle http transport. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + public SimpleAPI(HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : base(rootHandler, handlers) + { + Initialize(); + } + + /// + /// Initializes a new instance of the SimpleAPI class. + /// + /// + /// Optional. The base URI of the service. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + /// + /// Thrown when a required parameter is null + /// + public SimpleAPI(System.Uri baseUri, params DelegatingHandler[] handlers) : this(handlers) + { + if (baseUri == null) + { + throw new System.ArgumentNullException("baseUri"); + } + BaseUri = baseUri; + } + + /// + /// Initializes a new instance of the SimpleAPI 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. + /// + /// + /// Thrown when a required parameter is null + /// + public SimpleAPI(System.Uri baseUri, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : this(rootHandler, handlers) + { + if (baseUri == null) + { + throw new System.ArgumentNullException("baseUri"); + } + BaseUri = baseUri; + } + + /// + /// An optional partial-method to perform custom initialization. + /// + partial void CustomInitialize(); + /// + /// Initializes client properties. + /// + private void Initialize() + { + BaseUri = new System.Uri("http://test.swagger.io/v2"); + SerializationSettings = new JsonSerializerSettings + { + Formatting = Newtonsoft.Json.Formatting.Indented, + DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat, + DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc, + NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore, + ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver(), + Converters = new List + { + new Iso8601TimeSpanConverter() + } + }; + DeserializationSettings = new JsonSerializerSettings + { + DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat, + DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc, + NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore, + ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver(), + Converters = new List + { + new Iso8601TimeSpanConverter() + } + }; + CustomInitialize(); + } + } +} diff --git a/test/unit/Bug885.cs b/test/unit/Bug885.cs new file mode 100644 index 00000000000..61fe923856c --- /dev/null +++ b/test/unit/Bug885.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Bug885.Models; +using Xunit; + +namespace AutoRest.CSharp.Unit.Tests +{ + public class Bug885 + { + /// + /// https://github.com/Azure/autorest.csharp/issues/885 + /// Validate optional properties successfully. + /// + [Fact] + public async Task ValidateOptionalProperties() + { + var dateAfterModification = new DateAfterModification(); + dateAfterModification.Validate(); + } + } +} \ No newline at end of file