Skip to content

Commit d1f4332

Browse files
jimschubertviclovsky
authored andcommitted
[csharp] Support arrays of arrays for properties and models (swagger-api#7400)
* [csharp] Support composition on toJson Previous implementation assumed specification only supports polymorphic associations (via discrimator), although the code didn't seem to be setup correctly for that in the first place. That is, the parent object must define the discriminator (see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#models-with-polymorphism-support), so NOT HAS parent AND HAS discriminator doesn't make sense. From a C# perspective, base classes should have the method marked virtual and derived classes should override the method. This supports both composition and polymorphic definitions. * [csharp] this.Configuration in api template Unprefixed Configuration property access leads to ambiguous references when spec defines a Configuration model. * [csharp] Models/properties support nested arrays Previous implementation didn't support multiple levels of array with array items as OpenAPI spec supports. This means an object defined as type: array with items = type: array|items=double (which is common in GIS) would not be possible. This implementation assumes generics in the nested type definitions, so the above would generate List<List<double?>> for model parent types as well as property type declarations. * [csharp] Regenerate integration test sample * [csharp] Set "Client" case sensitive as reserved * [csharp] Regenerate security sample * [csharp] Regenerate samples
1 parent 02e87fc commit d1f4332

File tree

68 files changed

+1781
-501
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+1781
-501
lines changed

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AbstractCSharpCodegen.java

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,13 @@ public AbstractCSharpCodegen() {
6767
Arrays.asList("IDictionary")
6868
);
6969

70-
setReservedWordsLowerCase(
70+
// NOTE: C# uses camel cased reserved words, while models are title cased. We don't want lowercase comparisons.
71+
reservedWords.addAll(
7172
Arrays.asList(
7273
// set "client" as a reserved word to avoid conflicts with IO.Swagger.Client
7374
// this is a workaround and can be removed if c# api client is updated to use
7475
// fully qualified name
75-
"client", "parameter",
76+
"Client", "client", "parameter",
7677
// local variable names in API methods (endpoints)
7778
"localVarPath", "localVarPathParams", "localVarQueryParams", "localVarHeaderParams",
7879
"localVarFormParams", "localVarFileParams", "localVarStatusCode", "localVarResponse",
@@ -718,6 +719,12 @@ public String toDefaultValue(Property p) {
718719
return null;
719720
}
720721

722+
@Override
723+
protected boolean isReservedWord(String word) {
724+
// NOTE: This differs from super's implementation in that C# does _not_ want case insensitive matching.
725+
return reservedWords.contains(word);
726+
}
727+
721728
@Override
722729
public String getSwaggerType(Property p) {
723730
String swaggerType = super.getSwaggerType(p);
@@ -727,6 +734,8 @@ public String getSwaggerType(Property p) {
727734
swaggerType = ""; // set swagger type to empty string if null
728735
}
729736

737+
// NOTE: typeMapping here supports things like string/String, long/Long, datetime/DateTime as lowercase keys.
738+
// Should we require explicit casing here (values are not insensitive).
730739
// TODO avoid using toLowerCase as typeMapping should be case-sensitive
731740
if (typeMapping.containsKey(swaggerType.toLowerCase())) {
732741
type = typeMapping.get(swaggerType.toLowerCase());
@@ -739,16 +748,39 @@ public String getSwaggerType(Property p) {
739748
return toModelName(type);
740749
}
741750

751+
/**
752+
* Provides C# strongly typed declaration for simple arrays of some type and arrays of arrays of some type.
753+
* @param arr
754+
* @return
755+
*/
756+
private String getArrayTypeDeclaration(ArrayProperty arr) {
757+
// TODO: collection type here should be fully qualified namespace to avoid model conflicts
758+
// This supports arrays of arrays.
759+
String arrayType = typeMapping.get("array");
760+
StringBuilder instantiationType = new StringBuilder(arrayType);
761+
Property items = arr.getItems();
762+
String nestedType = getTypeDeclaration(items);
763+
// TODO: We may want to differentiate here between generics and primitive arrays.
764+
instantiationType.append("<").append(nestedType).append(">");
765+
return instantiationType.toString();
766+
}
767+
768+
@Override
769+
public String toInstantiationType(Property p) {
770+
if (p instanceof ArrayProperty) {
771+
return getArrayTypeDeclaration((ArrayProperty) p);
772+
}
773+
return super.toInstantiationType(p);
774+
}
775+
742776
@Override
743777
public String getTypeDeclaration(Property p) {
744778
if (p instanceof ArrayProperty) {
745-
ArrayProperty ap = (ArrayProperty) p;
746-
Property inner = ap.getItems();
747-
return getSwaggerType(p) + "<" + getTypeDeclaration(inner) + ">";
779+
return getArrayTypeDeclaration((ArrayProperty) p);
748780
} else if (p instanceof MapProperty) {
781+
// Should we also support maps of maps?
749782
MapProperty mp = (MapProperty) p;
750783
Property inner = mp.getAdditionalProperties();
751-
752784
return getSwaggerType(p) + "<string, " + getTypeDeclaration(inner) + ">";
753785
}
754786
return super.getTypeDeclaration(p);

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/AspNetCoreServerCodegen.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public AspNetCoreServerCodegen() {
3535
apiTemplateFiles.put("controller.mustache", ".cs");
3636

3737
// contextually reserved words
38-
setReservedWordsLowerCase(
38+
// NOTE: C# uses camel cased reserved words, while models are title cased. We don't want lowercase comparisons.
39+
reservedWords.addAll(
3940
Arrays.asList("var", "async", "await", "dynamic", "yield")
4041
);
4142

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/CSharpClientCodegen.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.samskivert.mustache.Mustache;
55
import io.swagger.codegen.*;
66
import io.swagger.models.Model;
7+
import io.swagger.models.properties.ArrayProperty;
8+
import io.swagger.models.properties.Property;
79
import org.slf4j.Logger;
810
import org.slf4j.LoggerFactory;
911

modules/swagger-codegen/src/test/java/io/swagger/codegen/csharp/CSharpModelTest.java

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,15 @@
99
import io.swagger.models.ModelImpl;
1010
import io.swagger.models.Path;
1111
import io.swagger.models.Swagger;
12-
import io.swagger.models.properties.ArrayProperty;
13-
import io.swagger.models.properties.DateTimeProperty;
14-
import io.swagger.models.properties.LongProperty;
15-
import io.swagger.models.properties.MapProperty;
16-
import io.swagger.models.properties.RefProperty;
17-
import io.swagger.models.properties.StringProperty;
12+
import io.swagger.models.properties.*;
1813
import io.swagger.parser.SwaggerParser;
1914

2015
import com.google.common.collect.Sets;
2116
import org.testng.Assert;
2217
import org.testng.annotations.Test;
2318

19+
import java.util.Arrays;
20+
import java.util.HashMap;
2421
import java.util.Map;
2522

2623
@SuppressWarnings("static-method")
@@ -331,4 +328,51 @@ public void mapModelTest() {
331328
Assert.assertEquals(cm.imports.size(), 1);
332329
Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("Children")).size(), 1);
333330
}
331+
332+
@Test(description = "convert an array of array models")
333+
public void arraysOfArraysModelTest() {
334+
final Model model = new ArrayModel()
335+
.description("a sample geolocation model")
336+
.items(
337+
new ArrayProperty().items(new DoubleProperty())
338+
);
339+
340+
final DefaultCodegen codegen = new CSharpClientCodegen();
341+
final CodegenModel cm = codegen.fromModel("sample", model);
342+
343+
Assert.assertEquals(cm.name, "sample");
344+
Assert.assertEquals(cm.classname, "Sample");
345+
Assert.assertEquals(cm.parent, "List<List<double?>>");
346+
}
347+
348+
@Test(description = "convert an array of array properties")
349+
public void arraysOfArraysPropertyTest() {
350+
final Model model = new ModelImpl()
351+
.description("a sample geolocation model")
352+
.property("points", new ArrayProperty()
353+
.items(
354+
new ArrayProperty().items(new DoubleProperty())
355+
)
356+
);
357+
358+
final DefaultCodegen codegen = new CSharpClientCodegen();
359+
final CodegenModel cm = codegen.fromModel("sample", model);
360+
361+
Assert.assertEquals(cm.name, "sample");
362+
Assert.assertEquals(cm.classname, "Sample");
363+
Assert.assertNull(cm.parent);
364+
365+
Assert.assertEquals(cm.vars.size(), 1);
366+
367+
final CodegenProperty property1 = cm.vars.get(0);
368+
Assert.assertEquals(property1.baseName, "points");
369+
Assert.assertNull(property1.complexType);
370+
Assert.assertEquals(property1.datatype, "List<List<double?>>");
371+
Assert.assertEquals(property1.name, "Points");
372+
Assert.assertEquals(property1.baseType, "List");
373+
Assert.assertEquals(property1.containerType, "array");
374+
Assert.assertFalse(property1.required);
375+
Assert.assertTrue(property1.isContainer);
376+
Assert.assertFalse(property1.isNotContainer);
377+
}
334378
}

samples/client/petstore-security-test/csharp/SwaggerClient/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ Class | Method | HTTP request | Description
101101
<a name="documentation-for-models"></a>
102102
## Documentation for Models
103103

104-
- [Model.ModelReturn](docs/ModelReturn.md)
104+
- [Model.Return](docs/Return.md)
105105

106106

107107
<a name="documentation-for-authorization"></a>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# IO.Swagger.Model.Return
2+
## Properties
3+
4+
Name | Type | Description | Notes
5+
------------ | ------------- | ------------- | -------------
6+
**_Return** | **int?** | property description *_/ &#39; \&quot; &#x3D;end - - \\r\\n \\n \\r | [optional]
7+
8+
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
9+
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Swagger Petstore *_/ ' \" =end - - \\r\\n \\n \\r
3+
*
4+
* This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\ *_/ ' \" =end - -
5+
*
6+
* OpenAPI spec version: 1.0.0 *_/ ' \" =end - - \\r\\n \\n \\r
7+
* Contact: [email protected] *_/ ' \" =end - - \\r\\n \\n \\r
8+
* Generated by: https://github.com/swagger-api/swagger-codegen.git
9+
*/
10+
11+
12+
using NUnit.Framework;
13+
14+
using System;
15+
using System.Linq;
16+
using System.IO;
17+
using System.Collections.Generic;
18+
using IO.Swagger.Api;
19+
using IO.Swagger.Model;
20+
using IO.Swagger.Client;
21+
using System.Reflection;
22+
using Newtonsoft.Json;
23+
24+
namespace IO.Swagger.Test
25+
{
26+
/// <summary>
27+
/// Class for testing Return
28+
/// </summary>
29+
/// <remarks>
30+
/// This file is automatically generated by Swagger Codegen.
31+
/// Please update the test case below to test the model.
32+
/// </remarks>
33+
[TestFixture]
34+
public class ReturnTests
35+
{
36+
// TODO uncomment below to declare an instance variable for Return
37+
//private Return instance;
38+
39+
/// <summary>
40+
/// Setup before each test
41+
/// </summary>
42+
[SetUp]
43+
public void Init()
44+
{
45+
// TODO uncomment below to create an instance of Return
46+
//instance = new Return();
47+
}
48+
49+
/// <summary>
50+
/// Clean up after each test
51+
/// </summary>
52+
[TearDown]
53+
public void Cleanup()
54+
{
55+
56+
}
57+
58+
/// <summary>
59+
/// Test an instance of Return
60+
/// </summary>
61+
[Test]
62+
public void ReturnInstanceTest()
63+
{
64+
// TODO uncomment below to test "IsInstanceOfType" Return
65+
//Assert.IsInstanceOfType<Return> (instance, "variable 'instance' is a Return");
66+
}
67+
68+
69+
/// <summary>
70+
/// Test the property '_Return'
71+
/// </summary>
72+
[Test]
73+
public void _ReturnTest()
74+
{
75+
// TODO unit test for the property '_Return'
76+
}
77+
78+
}
79+
80+
}

samples/client/petstore-security-test/csharp/SwaggerClient/src/IO.Swagger/Api/FakeApi.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public partial class FakeApi : IFakeApi
8383
/// <returns></returns>
8484
public FakeApi(String basePath)
8585
{
86-
this.Configuration = new Configuration { BasePath = basePath };
86+
this.Configuration = new IO.Swagger.Client.Configuration { BasePath = basePath };
8787

8888
ExceptionFactory = IO.Swagger.Client.Configuration.DefaultExceptionFactory;
8989
}
@@ -94,10 +94,10 @@ public FakeApi(String basePath)
9494
/// </summary>
9595
/// <param name="configuration">An instance of Configuration</param>
9696
/// <returns></returns>
97-
public FakeApi(Configuration configuration = null)
97+
public FakeApi(IO.Swagger.Client.Configuration configuration = null)
9898
{
9999
if (configuration == null) // use the default one in Configuration
100-
this.Configuration = Configuration.Default;
100+
this.Configuration = IO.Swagger.Client.Configuration.Default;
101101
else
102102
this.Configuration = configuration;
103103

@@ -127,7 +127,7 @@ public void SetBasePath(String basePath)
127127
/// Gets or sets the configuration object
128128
/// </summary>
129129
/// <value>An instance of the Configuration</value>
130-
public Configuration Configuration {get; set;}
130+
public IO.Swagger.Client.Configuration Configuration {get; set;}
131131

132132
/// <summary>
133133
/// Provides a factory method hook for the creation of exceptions.
@@ -190,7 +190,7 @@ public ApiResponse<Object> TestCodeInjectEndRnNRWithHttpInfo (string testCodeInj
190190
var localVarPath = "/fake";
191191
var localVarPathParams = new Dictionary<String, String>();
192192
var localVarQueryParams = new List<KeyValuePair<String, String>>();
193-
var localVarHeaderParams = new Dictionary<String, String>(Configuration.DefaultHeader);
193+
var localVarHeaderParams = new Dictionary<String, String>(this.Configuration.DefaultHeader);
194194
var localVarFormParams = new Dictionary<String, String>();
195195
var localVarFileParams = new Dictionary<String, FileParameter>();
196196
Object localVarPostBody = null;
@@ -200,22 +200,22 @@ public ApiResponse<Object> TestCodeInjectEndRnNRWithHttpInfo (string testCodeInj
200200
"application/json",
201201
"*_/ ' =end - - "
202202
};
203-
String localVarHttpContentType = Configuration.ApiClient.SelectHeaderContentType(localVarHttpContentTypes);
203+
String localVarHttpContentType = this.Configuration.ApiClient.SelectHeaderContentType(localVarHttpContentTypes);
204204

205205
// to determine the Accept header
206206
String[] localVarHttpHeaderAccepts = new String[] {
207207
"application/json",
208208
"*_/ ' =end - - "
209209
};
210-
String localVarHttpHeaderAccept = Configuration.ApiClient.SelectHeaderAccept(localVarHttpHeaderAccepts);
210+
String localVarHttpHeaderAccept = this.Configuration.ApiClient.SelectHeaderAccept(localVarHttpHeaderAccepts);
211211
if (localVarHttpHeaderAccept != null)
212212
localVarHeaderParams.Add("Accept", localVarHttpHeaderAccept);
213213

214-
if (testCodeInjectEndRnNR != null) localVarFormParams.Add("test code inject */ &#39; &quot; &#x3D;end -- \r\n \n \r", Configuration.ApiClient.ParameterToString(testCodeInjectEndRnNR)); // form parameter
214+
if (testCodeInjectEndRnNR != null) localVarFormParams.Add("test code inject */ &#39; &quot; &#x3D;end -- \r\n \n \r", this.Configuration.ApiClient.ParameterToString(testCodeInjectEndRnNR)); // form parameter
215215

216216

217217
// make the HTTP request
218-
IRestResponse localVarResponse = (IRestResponse) Configuration.ApiClient.CallApi(localVarPath,
218+
IRestResponse localVarResponse = (IRestResponse) this.Configuration.ApiClient.CallApi(localVarPath,
219219
Method.PUT, localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarFormParams, localVarFileParams,
220220
localVarPathParams, localVarHttpContentType);
221221

@@ -256,7 +256,7 @@ public async System.Threading.Tasks.Task<ApiResponse<Object>> TestCodeInjectEndR
256256
var localVarPath = "/fake";
257257
var localVarPathParams = new Dictionary<String, String>();
258258
var localVarQueryParams = new List<KeyValuePair<String, String>>();
259-
var localVarHeaderParams = new Dictionary<String, String>(Configuration.DefaultHeader);
259+
var localVarHeaderParams = new Dictionary<String, String>(this.Configuration.DefaultHeader);
260260
var localVarFormParams = new Dictionary<String, String>();
261261
var localVarFileParams = new Dictionary<String, FileParameter>();
262262
Object localVarPostBody = null;
@@ -266,22 +266,22 @@ public async System.Threading.Tasks.Task<ApiResponse<Object>> TestCodeInjectEndR
266266
"application/json",
267267
"*_/ ' =end - - "
268268
};
269-
String localVarHttpContentType = Configuration.ApiClient.SelectHeaderContentType(localVarHttpContentTypes);
269+
String localVarHttpContentType = this.Configuration.ApiClient.SelectHeaderContentType(localVarHttpContentTypes);
270270

271271
// to determine the Accept header
272272
String[] localVarHttpHeaderAccepts = new String[] {
273273
"application/json",
274274
"*_/ ' =end - - "
275275
};
276-
String localVarHttpHeaderAccept = Configuration.ApiClient.SelectHeaderAccept(localVarHttpHeaderAccepts);
276+
String localVarHttpHeaderAccept = this.Configuration.ApiClient.SelectHeaderAccept(localVarHttpHeaderAccepts);
277277
if (localVarHttpHeaderAccept != null)
278278
localVarHeaderParams.Add("Accept", localVarHttpHeaderAccept);
279279

280-
if (testCodeInjectEndRnNR != null) localVarFormParams.Add("test code inject */ &#39; &quot; &#x3D;end -- \r\n \n \r", Configuration.ApiClient.ParameterToString(testCodeInjectEndRnNR)); // form parameter
280+
if (testCodeInjectEndRnNR != null) localVarFormParams.Add("test code inject */ &#39; &quot; &#x3D;end -- \r\n \n \r", this.Configuration.ApiClient.ParameterToString(testCodeInjectEndRnNR)); // form parameter
281281

282282

283283
// make the HTTP request
284-
IRestResponse localVarResponse = (IRestResponse) await Configuration.ApiClient.CallApiAsync(localVarPath,
284+
IRestResponse localVarResponse = (IRestResponse) await this.Configuration.ApiClient.CallApiAsync(localVarPath,
285285
Method.PUT, localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarFormParams, localVarFileParams,
286286
localVarPathParams, localVarHttpContentType);
287287

0 commit comments

Comments
 (0)