Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discriminator in Open API JSON Causes "Type expected" Error w/RTK Query Code Gen #4791

Open
fuzl-llc opened this issue Dec 27, 2024 · 1 comment

Comments

@fuzl-llc
Copy link

I have an API and use NSwag.AspNetCore to generate open api JSON. I have a "WidgetBase" abstract type with several derived types that are distinquished by a WidgetType property. The generated JSON used to be this:

      "WidgetBase": {
        "type": "object",
        "x-abstract": true,
        "additionalProperties": false,
        "properties": {
          "widgetType": {
            "type": "string"
          }
        }
      }

With the above, RTK Query Code Gen runs perfectly fine.

I updated to a newer version of NSwag and it now adds some additional info (discriminator, required/$type, and properties/$type):

      "WidgetBase": {
          "type": "object",
          "discriminator": {
              "propertyName": "$type"
          },
          "x-abstract": true,
          "additionalProperties": false,
          "required": [
              "$type"
          ],
          "properties": {
              "widgetType": {
                  "type": "string"
              },
              "$type": {
                  "type": "string"
              }
          }
      }

Now when I run RTK Query Code Gen, I get this error:

Generating ./fuzlApiTypes.ts
SyntaxError: Type expected. (152:26)
�[0m �[90m 150 |�[39m     errorMessage�[33m?�[39m�[33m:�[39m string�[33m;�[39m
 �[90m 151 |�[39m }�[33m;�[39m
�[31m�[1m>�[22m�[39m�[90m 152 |�[39m �[36mexport�[39m type �[33mWidgetBase�[39m �[33m=�[39m �[33m;�[39m
 �[90m     |�[39m                          �[31m�[1m^�[22m�[39m
 �[90m 153 |�[39m �[36mexport�[39m type �[33mWidgetDrawer�[39m �[33m=�[39m {
 �[90m 154 |�[39m     widgets�[33m?�[39m�[33m:�[39m �[33mWidgetBase�[39m[]�[33m;�[39m
 �[90m 155 |�[39m }�[33m;�[39m�[0m
    at _4 (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/plugins/typescript.mjs:17:70560)
    at N4 (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/plugins/typescript.mjs:20:955)
    at Object.M4 [as parse] (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/plugins/typescript.mjs:20:1477)
    at parse5 (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/index.mjs:19838:24)
    at coreFormat (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/index.mjs:20386:25)
    at formatWithCursor (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/index.mjs:20598:14)
    at Object.format2 (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/index.mjs:22002:25)
    at generateEndpoints (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/@rtk-query/codegen-openapi/src/index.ts:25:7)
    at v (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/@rtk-query/codegen-openapi/src/bin/cli.ts:59:7) {
  loc: { start: { line: 152, column: 26 }, end: { line: 152, column: 26 } },
  cause: $f: Type expected.
      at ed (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/plugins/typescript.mjs:17:8588)
      at ad (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/plugins/typescript.mjs:17:10524)
      at Xh (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/plugins/typescript.mjs:17:65505)
      at a4 (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/plugins/typescript.mjs:17:70421)
      at l0 (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/plugins/typescript.mjs:17:70163)
      at file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/plugins/typescript.mjs:20:1441
      at s4 (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/plugins/typescript.mjs:17:70714)
      at Object.M4 [as parse] (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/plugins/typescript.mjs:20:1425)
      at parse5 (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/index.mjs:19838:24)
      at coreFormat (file:///C:/Dev/Code/Fuzl/Apps/client/node_modules/prettier/index.mjs:20386:25) {
    fileName: 'estree.ts',
    location: { end: [Object], start: [Object] }
  },
  codeFrame: '\x1B[0m \x1B[90m 150 |\x1B[39m     errorMessage\x1B[33m?\x1B[39m\x1B[33m:\x1B[39m string\x1B[33m;\x1B[39m\n' +
    ' \x1B[90m 151 |\x1B[39m }\x1B[33m;\x1B[39m\n' +
    '\x1B[31m\x1B[1m>\x1B[22m\x1B[39m\x1B[90m 152 |\x1B[39m \x1B[36mexport\x1B[39m type \x1B[33mWidgetBase\x1B[39m \x1B[33m=\x1B[39m \x1B[33m;\x1B[39m\n' +
    ' \x1B[90m     |\x1B[39m                          \x1B[31m\x1B[1m^\x1B[22m\x1B[39m\n' +
    ' \x1B[90m 153 |\x1B[39m \x1B[36mexport\x1B[39m type \x1B[33mWidgetDrawer\x1B[39m \x1B[33m=\x1B[39m {\n' +
    ' \x1B[90m 154 |\x1B[39m     widgets\x1B[33m?\x1B[39m\x1B[33m:\x1B[39m \x1B[33mWidgetBase\x1B[39m[]\x1B[33m;\x1B[39m\n' +
    ' \x1B[90m 155 |\x1B[39m }\x1B[33m;\x1B[39m\x1B[0m'
}

I can't make much sense of this but see "SyntaxError: Type expected" and "WidgetBase" mentioned. The JSON seems to indicate that I have a "type" property on WidgetBase which I do not so I tried to make discriminator/propertyName referernce the widgetType property by adding JsonPolymorphicAttribute to my WidgetBase type in the C# code:

    [JsonPolymorphic(TypeDiscriminatorPropertyName = "widgetType")]
    [JsonDerivedType(typeof(Widget2))]
    [JsonDerivedType(typeof(Widget2))]
    public abstract class WidgetBase
    {
        public string WidgetType { get; set; } = String.Empty;
    }

That then resulted in this, seemingly better, JSON that correctly says that "widgetType" is the thing that can be used to discriminate between derived types:

      "WidgetBase": {
        "type": "object",
        "discriminator": {
          "propertyName": "widgetType"
        },
        "x-abstract": true,
        "additionalProperties": false,
        "required": [
          "widgetType"
        ],
        "properties": {
          "widgetType": {
            "type": "string"
          }
        }
      }

This seemed better since the WidgetBase type does have a WidgetType property. That property is Pascal case in C# and camel case in the generated client code and I tried both for the JsonPolymorphicAttribute just in case but either way I get basically the same error when I try to generate code with the RTK Query Code Gen.

Does RTK Query Code Gen support this discriminator value? I could be doing something wrong, but it seems to break when nothing really changes except adding the extra discriminator and related JSON to the API spec.

@fuzl-llc
Copy link
Author

I have it working now. I don't fully understand it but thought I'd post in case it helps someone and/or there is anything to be done on the code gen part. I'm guessing not since it seems to do with how I was using NJsonSchema and the resulting Open API JSON document's format... though perhaps the error above could be made to be more descriptive - it didn't really help me much but maybe that is my lack of understanding...

Anyway, I'd put the [JsonSchemaFlatten] on my derived types a long time ago for some reason. I remove those and adjusted the way I was using the [JsonDerivedType] attribute on the base class. I also removed the [TypeDiscriminatorPropertyName] attribute to accept the default "$type" value:

[JsonDerivedType(typeof(Widget1), "Widget1")]
[JsonDerivedType(typeof(Widget2), "Widget2")]

These changes resulted in a more expected Open API JSON output which included references to derived types in a mapping from the discriminator value to the type:

      "WidgetBase": {
        "type": "object",
        "discriminator": {
          "propertyName": "$type",
          "mapping": {
            "Widget1": "#/components/schemas/Widget1",
            "Widget2": "#/components/schemas/Widget2",
          }
        },
        "x-abstract": true,
        "additionalProperties": false,
        "required": [
          "$type"
        ],
        "properties": {
          "widgetType": {
            "type": "string"
          },
          "$type": {
            "type": "string"
          }
        }
      }

...as well as the allOf construct to include base type properties in the derived types:

      "Widget1": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WidgetBase"
          },
          {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "body": {
                "type": "string"
              }
            }
          }
        ]
      }

Then the RTK Query Open API gen worked just fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant