Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/configs/csharp-generichost-net8-nrt.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# for csharp generichost
generatorName: csharp
outputDir: samples/client/petstore/csharp/generichost/net8/NullReferenceTypes
inputSpec: modules/openapi-generator/src/test/resources/3_0/csharp/petstore-with-fake-endpoints-models-for-testing-with-http-signature.yaml
inputSpec: modules/openapi-generator/src/test/resources/3_0/csharp/petstore-with-fake-endpoints-models-for-testing-with-null-reference-types.yaml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is different about this new spec? Can you just put the diff in the http-signature spec and revert this? If not, I suggest leaving the new spec and adding a new file to bin/configs, but still reverting this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the new spec I have added objects that are inline aliases.

list:
  items:
    type: string
  type: array
deepList:
  items:
    items:
      type: string
    type: array
  type: array

I initially placed it in the original specification, but since that was used everywhere it caused such a huge delta that I thought it was clearer to isolate the representation in a separate specification.

But if we prefer to have fewer specifications that represent a wider array of scenarios I can change it so that these models are included in the main specification.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer fewer specs. If it doesnt work in RestSharp or HttpClient then we can use a new spec minimized as much as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I'll look into reverting the new specification and instead extending the existing one.

A question on another topic, how should we best handle the scenarios that have been presented here?

TL;DR: The NULLABLE_REFERENCE_TYPES has more scenarios now with deep aliases. These are difficult to represent with the current approach. How should one best leave them out to make it clear that they are not supported? Is the GitHub PR and issue ticket and our discussion here sufficient?

Longer: Previously the Csharp generator did not support deep aliases (more than one in depth), it failed by generating code that accidentally compiled (it just made the type List). Now with support for deep aliases, there are all of a sudden a lot more of possible NULLABLE_REFERENCE_TYPES scenarios (as per the comment linked earlier).

My current understanding of how the setting is realized is that it injects the config with the mustache file and !nrt at the end of the type. This has previously worked, since the depth was only one (so either inject it at the end, or not). Now with a dynamic depth, this is not really suitable for a mustache config anymore. It would be easier and clearer if the type was completely calculated in the Codegen. Implementing and testing this is a large task, and not something I can myself fix easily. I wonder how to best handle this?

The current solution presented would generate valid code, it is just that it will be Option<List<List<string>>?> when it should have been Option<List<List<string>?>?> for deep aliases (i.e., any ? that isn't the last will be omitted).

But as previously stated, this has never been supported before to begin with, so is it something that one should consider too much?

templateDir: modules/openapi-generator/src/main/resources/csharp
additionalProperties:
packageGuid: '{321C8C3F-0156-40C1-AE42-D59761FB9B6C}'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1624,7 +1624,7 @@ public String getTypeDeclaration(Schema p) {
Schema<?> target = ModelUtils.isGenerateAliasAsModel() ? p : schema;
if (ModelUtils.isArraySchema(target)) {
Schema<?> items = getSchemaItems(schema);
return getSchemaType(target) + "<" + getTypeDeclarationForArray(items) + ">";
return typeMapping.get("array") + "<" + getTypeDeclarationForArray(items) + ">";
Copy link
Contributor Author

@Mattias-Sehlstedt Mattias-Sehlstedt Oct 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reverted this type calculation to the previous static array-mapping, since the more dynamic .getSchemaType(target) could modify the type with ? based upon NULLABLE_REFERENCE_TYPES (which is undesirable).

} else if (ModelUtils.isMapSchema(p)) {
// Should we also support maps of maps?
Schema<?> inner = ModelUtils.getAdditionalProperties(p);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,11 +368,19 @@ public void nullablePropertyWithoutNullableReferenceTypesTest() {
public void nullablePropertyWithNullableReferenceTypesTest() {
final Schema model = new Schema()
.description("a sample model")
.addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT).nullable(true))
.addProperties("id", new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT)
.nullable(true))
.addProperties("urls", new ArraySchema()
.items(new StringSchema()).nullable(true))
.items(new StringSchema())
.nullable(true))
.addProperties("name", new StringSchema().nullable(true))
.addProperties("subObject", new Schema().addProperties("name", new StringSchema()).nullable(true))
.addProperties("subObject", new Schema().addProperties("name", new StringSchema())
.nullable(true))
.addProperties("deepAliasArray", new ArraySchema()
.items(new ArraySchema()
.items(new StringSchema())
.nullable(true))
.nullable(true))
.addRequiredItem("id");
final DefaultCodegen codegen = new AspNetServerCodegen();
codegen.processOpts();
Expand All @@ -385,7 +393,7 @@ public void nullablePropertyWithNullableReferenceTypesTest() {
Assert.assertEquals(cm.name, "sample");
Assert.assertEquals(cm.classname, "Sample");
Assert.assertEquals(cm.description, "a sample model");
Assert.assertEquals(cm.vars.size(), 4);
Assert.assertEquals(cm.vars.size(), 5);

final CodegenProperty property1 = cm.vars.get(0);
Assert.assertEquals(property1.baseName, "id");
Expand All @@ -398,7 +406,7 @@ public void nullablePropertyWithNullableReferenceTypesTest() {

final CodegenProperty property2 = cm.vars.get(1);
Assert.assertEquals(property2.baseName, "urls");
Assert.assertEquals(property2.dataType, "List?<string>");
Assert.assertEquals(property2.dataType, "List<string>");
Assert.assertEquals(property2.name, "Urls");
Assert.assertNull(property2.defaultValue);
Assert.assertEquals(property2.baseType, "List?");
Expand All @@ -424,6 +432,17 @@ public void nullablePropertyWithNullableReferenceTypesTest() {
Assert.assertEquals(property4.baseType, "Object?");
Assert.assertFalse(property4.required);
Assert.assertFalse(property4.isPrimitiveType);

final CodegenProperty property5 = cm.vars.get(4);
Assert.assertEquals(property5.baseName, "deepAliasArray");
Assert.assertEquals(property5.dataType, "List<List<string>>");
Assert.assertEquals(property5.name, "DeepAliasArray");
Assert.assertNull(property5.defaultValue);
Assert.assertEquals(property5.baseType, "List?");
Assert.assertEquals(property5.containerType, "array");
Assert.assertFalse(property5.required);
Assert.assertFalse(property5.isPrimitiveType);
Assert.assertTrue(property5.isContainer);
}

@Test(description = "convert a model with list property")
Expand Down
Loading
Loading