Skip to content

Commit

Permalink
[csharp] Support arrays of arrays for properties and models (swagger-…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
jimschubert authored and viclovsky committed Jan 23, 2018
1 parent 02e87fc commit d1f4332
Show file tree
Hide file tree
Showing 68 changed files with 1,781 additions and 501 deletions.
Expand Up @@ -67,12 +67,13 @@ public AbstractCSharpCodegen() {
Arrays.asList("IDictionary")
);

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

@Override
protected boolean isReservedWord(String word) {
// NOTE: This differs from super's implementation in that C# does _not_ want case insensitive matching.
return reservedWords.contains(word);
}

@Override
public String getSwaggerType(Property p) {
String swaggerType = super.getSwaggerType(p);
Expand All @@ -727,6 +734,8 @@ public String getSwaggerType(Property p) {
swaggerType = ""; // set swagger type to empty string if null
}

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

/**
* Provides C# strongly typed declaration for simple arrays of some type and arrays of arrays of some type.
* @param arr
* @return
*/
private String getArrayTypeDeclaration(ArrayProperty arr) {
// TODO: collection type here should be fully qualified namespace to avoid model conflicts
// This supports arrays of arrays.
String arrayType = typeMapping.get("array");
StringBuilder instantiationType = new StringBuilder(arrayType);
Property items = arr.getItems();
String nestedType = getTypeDeclaration(items);
// TODO: We may want to differentiate here between generics and primitive arrays.
instantiationType.append("<").append(nestedType).append(">");
return instantiationType.toString();
}

@Override
public String toInstantiationType(Property p) {
if (p instanceof ArrayProperty) {
return getArrayTypeDeclaration((ArrayProperty) p);
}
return super.toInstantiationType(p);
}

@Override
public String getTypeDeclaration(Property p) {
if (p instanceof ArrayProperty) {
ArrayProperty ap = (ArrayProperty) p;
Property inner = ap.getItems();
return getSwaggerType(p) + "<" + getTypeDeclaration(inner) + ">";
return getArrayTypeDeclaration((ArrayProperty) p);
} else if (p instanceof MapProperty) {
// Should we also support maps of maps?
MapProperty mp = (MapProperty) p;
Property inner = mp.getAdditionalProperties();

return getSwaggerType(p) + "<string, " + getTypeDeclaration(inner) + ">";
}
return super.getTypeDeclaration(p);
Expand Down
Expand Up @@ -35,7 +35,8 @@ public AspNetCoreServerCodegen() {
apiTemplateFiles.put("controller.mustache", ".cs");

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

Expand Down
Expand Up @@ -4,6 +4,8 @@
import com.samskivert.mustache.Mustache;
import io.swagger.codegen.*;
import io.swagger.models.Model;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down
Expand Up @@ -9,18 +9,15 @@
import io.swagger.models.ModelImpl;
import io.swagger.models.Path;
import io.swagger.models.Swagger;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.DateTimeProperty;
import io.swagger.models.properties.LongProperty;
import io.swagger.models.properties.MapProperty;
import io.swagger.models.properties.RefProperty;
import io.swagger.models.properties.StringProperty;
import io.swagger.models.properties.*;
import io.swagger.parser.SwaggerParser;

import com.google.common.collect.Sets;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@SuppressWarnings("static-method")
Expand Down Expand Up @@ -331,4 +328,51 @@ public void mapModelTest() {
Assert.assertEquals(cm.imports.size(), 1);
Assert.assertEquals(Sets.intersection(cm.imports, Sets.newHashSet("Children")).size(), 1);
}

@Test(description = "convert an array of array models")
public void arraysOfArraysModelTest() {
final Model model = new ArrayModel()
.description("a sample geolocation model")
.items(
new ArrayProperty().items(new DoubleProperty())
);

final DefaultCodegen codegen = new CSharpClientCodegen();
final CodegenModel cm = codegen.fromModel("sample", model);

Assert.assertEquals(cm.name, "sample");
Assert.assertEquals(cm.classname, "Sample");
Assert.assertEquals(cm.parent, "List<List<double?>>");
}

@Test(description = "convert an array of array properties")
public void arraysOfArraysPropertyTest() {
final Model model = new ModelImpl()
.description("a sample geolocation model")
.property("points", new ArrayProperty()
.items(
new ArrayProperty().items(new DoubleProperty())
)
);

final DefaultCodegen codegen = new CSharpClientCodegen();
final CodegenModel cm = codegen.fromModel("sample", model);

Assert.assertEquals(cm.name, "sample");
Assert.assertEquals(cm.classname, "Sample");
Assert.assertNull(cm.parent);

Assert.assertEquals(cm.vars.size(), 1);

final CodegenProperty property1 = cm.vars.get(0);
Assert.assertEquals(property1.baseName, "points");
Assert.assertNull(property1.complexType);
Assert.assertEquals(property1.datatype, "List<List<double?>>");
Assert.assertEquals(property1.name, "Points");
Assert.assertEquals(property1.baseType, "List");
Assert.assertEquals(property1.containerType, "array");
Assert.assertFalse(property1.required);
Assert.assertTrue(property1.isContainer);
Assert.assertFalse(property1.isNotContainer);
}
}
Expand Up @@ -101,7 +101,7 @@ Class | Method | HTTP request | Description
<a name="documentation-for-models"></a>
## Documentation for Models

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


<a name="documentation-for-authorization"></a>
Expand Down
@@ -0,0 +1,9 @@
# IO.Swagger.Model.Return
## Properties

Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**_Return** | **int?** | property description *_/ &#39; \&quot; &#x3D;end - - \\r\\n \\n \\r | [optional]

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

@@ -0,0 +1,80 @@
/*
* Swagger Petstore *_/ ' \" =end - - \\r\\n \\n \\r
*
* 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 - -
*
* OpenAPI spec version: 1.0.0 *_/ ' \" =end - - \\r\\n \\n \\r
* Contact: apiteam@swagger.io *_/ ' \" =end - - \\r\\n \\n \\r
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*/


using NUnit.Framework;

using System;
using System.Linq;
using System.IO;
using System.Collections.Generic;
using IO.Swagger.Api;
using IO.Swagger.Model;
using IO.Swagger.Client;
using System.Reflection;
using Newtonsoft.Json;

namespace IO.Swagger.Test
{
/// <summary>
/// Class for testing Return
/// </summary>
/// <remarks>
/// This file is automatically generated by Swagger Codegen.
/// Please update the test case below to test the model.
/// </remarks>
[TestFixture]
public class ReturnTests
{
// TODO uncomment below to declare an instance variable for Return
//private Return instance;

/// <summary>
/// Setup before each test
/// </summary>
[SetUp]
public void Init()
{
// TODO uncomment below to create an instance of Return
//instance = new Return();
}

/// <summary>
/// Clean up after each test
/// </summary>
[TearDown]
public void Cleanup()
{

}

/// <summary>
/// Test an instance of Return
/// </summary>
[Test]
public void ReturnInstanceTest()
{
// TODO uncomment below to test "IsInstanceOfType" Return
//Assert.IsInstanceOfType<Return> (instance, "variable 'instance' is a Return");
}


/// <summary>
/// Test the property '_Return'
/// </summary>
[Test]
public void _ReturnTest()
{
// TODO unit test for the property '_Return'
}

}

}
Expand Up @@ -83,7 +83,7 @@ public partial class FakeApi : IFakeApi
/// <returns></returns>
public FakeApi(String basePath)
{
this.Configuration = new Configuration { BasePath = basePath };
this.Configuration = new IO.Swagger.Client.Configuration { BasePath = basePath };

ExceptionFactory = IO.Swagger.Client.Configuration.DefaultExceptionFactory;
}
Expand All @@ -94,10 +94,10 @@ public FakeApi(String basePath)
/// </summary>
/// <param name="configuration">An instance of Configuration</param>
/// <returns></returns>
public FakeApi(Configuration configuration = null)
public FakeApi(IO.Swagger.Client.Configuration configuration = null)
{
if (configuration == null) // use the default one in Configuration
this.Configuration = Configuration.Default;
this.Configuration = IO.Swagger.Client.Configuration.Default;
else
this.Configuration = configuration;

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

/// <summary>
/// Provides a factory method hook for the creation of exceptions.
Expand Down Expand Up @@ -190,7 +190,7 @@ public ApiResponse<Object> TestCodeInjectEndRnNRWithHttpInfo (string testCodeInj
var localVarPath = "/fake";
var localVarPathParams = new Dictionary<String, String>();
var localVarQueryParams = new List<KeyValuePair<String, String>>();
var localVarHeaderParams = new Dictionary<String, String>(Configuration.DefaultHeader);
var localVarHeaderParams = new Dictionary<String, String>(this.Configuration.DefaultHeader);
var localVarFormParams = new Dictionary<String, String>();
var localVarFileParams = new Dictionary<String, FileParameter>();
Object localVarPostBody = null;
Expand All @@ -200,22 +200,22 @@ public ApiResponse<Object> TestCodeInjectEndRnNRWithHttpInfo (string testCodeInj
"application/json",
"*_/ ' =end - - "
};
String localVarHttpContentType = Configuration.ApiClient.SelectHeaderContentType(localVarHttpContentTypes);
String localVarHttpContentType = this.Configuration.ApiClient.SelectHeaderContentType(localVarHttpContentTypes);

// to determine the Accept header
String[] localVarHttpHeaderAccepts = new String[] {
"application/json",
"*_/ ' =end - - "
};
String localVarHttpHeaderAccept = Configuration.ApiClient.SelectHeaderAccept(localVarHttpHeaderAccepts);
String localVarHttpHeaderAccept = this.Configuration.ApiClient.SelectHeaderAccept(localVarHttpHeaderAccepts);
if (localVarHttpHeaderAccept != null)
localVarHeaderParams.Add("Accept", localVarHttpHeaderAccept);

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


// make the HTTP request
IRestResponse localVarResponse = (IRestResponse) Configuration.ApiClient.CallApi(localVarPath,
IRestResponse localVarResponse = (IRestResponse) this.Configuration.ApiClient.CallApi(localVarPath,
Method.PUT, localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarFormParams, localVarFileParams,
localVarPathParams, localVarHttpContentType);

Expand Down Expand Up @@ -256,7 +256,7 @@ public async System.Threading.Tasks.Task<ApiResponse<Object>> TestCodeInjectEndR
var localVarPath = "/fake";
var localVarPathParams = new Dictionary<String, String>();
var localVarQueryParams = new List<KeyValuePair<String, String>>();
var localVarHeaderParams = new Dictionary<String, String>(Configuration.DefaultHeader);
var localVarHeaderParams = new Dictionary<String, String>(this.Configuration.DefaultHeader);
var localVarFormParams = new Dictionary<String, String>();
var localVarFileParams = new Dictionary<String, FileParameter>();
Object localVarPostBody = null;
Expand All @@ -266,22 +266,22 @@ public async System.Threading.Tasks.Task<ApiResponse<Object>> TestCodeInjectEndR
"application/json",
"*_/ ' =end - - "
};
String localVarHttpContentType = Configuration.ApiClient.SelectHeaderContentType(localVarHttpContentTypes);
String localVarHttpContentType = this.Configuration.ApiClient.SelectHeaderContentType(localVarHttpContentTypes);

// to determine the Accept header
String[] localVarHttpHeaderAccepts = new String[] {
"application/json",
"*_/ ' =end - - "
};
String localVarHttpHeaderAccept = Configuration.ApiClient.SelectHeaderAccept(localVarHttpHeaderAccepts);
String localVarHttpHeaderAccept = this.Configuration.ApiClient.SelectHeaderAccept(localVarHttpHeaderAccepts);
if (localVarHttpHeaderAccept != null)
localVarHeaderParams.Add("Accept", localVarHttpHeaderAccept);

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


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

Expand Down

0 comments on commit d1f4332

Please sign in to comment.