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

Springwolf ui can't mapped oneOf message. Error parsing AsyncAPI message of channel. #1133

Open
FanatTokyoGhoul opened this issue Dec 18, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@FanatTokyoGhoul
Copy link

FanatTokyoGhoul commented Dec 18, 2024

Describe the bug
When i generate oneOf message schema for springwolf-ui i get exception - "Error parsing AsyncAPI message of channel queue: Schema com.example.request.Request1 not found".

Dependencies and versions used

  • springwolf-sqs version 1.9.0
  • springwolf-ui version 1.9.0
  • kotlin version 2.0.21
  • spring version 3.3.5 and dependency manager version 1.1.5

Code Examples

Spring SqsListener

I use only AsyncListener. Sqs listener i disable in application.yml. This example for my sqs listener.

@Component
class QueueListener(
    private val service: Service
) {

    @SqsListener(
        value = ["\${amazon.sqs.queue}"],
    )
    @AsyncListener(
        operation = AsyncOperation(
            headers = AsyncOperation.Headers(
                schemaName = "Request id header",
                values = [AsyncOperation.Headers.Header(
                    name = "request-id",
                    description = "Generate from sqs sender"
                )]
            ),
            channelName = "queue",
            description = "some description",
            message = AsyncMessage(
                messageId = "com.example.request.BaseRequest"
            )
        )
    )
    @SqsAsyncOperationBinding(queues = [SqsAsyncQueueBinding(name = "\${amazon.sqs.queue}")])
    fun onMessage(request: LabelDetectRequest) {

        when (request) {
            is ReqestExample1-> service.mapRequest1(request)
            is ReqestExample2 -> service.mapRequest2(request)
        }
    }
}

Dto class examples

enum class RequestEnum{
    REQUEST_1, REQUEST_2
}


@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.EXISTING_PROPERTY,
    property = "type"
)
@JsonSubTypes(
    JsonSubTypes.Type(
        value = Request1::class,
        name = "REQUEST_1"
    ),
    JsonSubTypes.Type(
        value = Request2::class,
        name = "REQUEST_2"
    )
)
@Schema(
    description = "Base class for request",
    discriminatorProperty = "type",
    oneOf = [Request1::class, Request2::class]
)
sealed class Request(

    @field:Schema(description = "Enum for detected label type", example = "REQUEST_1")
    open val type: RequestEnum = RequestEnum.REQUEST_1
)

@Schema(
    description = "Class for request 1"
)
data class Request1(

    @Schema(description = "Job id from rekognition", example = "00000000-0000-0000-0000-000000000000")
    val jobId: String,

    @Schema(description = "UUID of project", example = "00000000-0000-0000-0000-000000000000")
    val objectId: String,

    @Schema(hidden = true)
    override val type: RequestEnum= RequestEnum.REQUEST_1
) : Request(type)

data class Request2(

    @Schema(description = "UUID of project", example = "00000000-0000-0000-0000-000000000000")
    val objectId: String,

    @Schema(description = "List labels from rekognition")
    val moderationLabels: List<RequestLabel>,

    @Schema(hidden = true)
    override val type: RequestEnum= RequestEnum.REQUEST_2
) : Request(type)

@Schema(description = "Class for label description from rekognition")
data class RequestLabel(

    @JsonProperty("Name")
    @Schema(description = "Label name", example = "Human")
    val name: String?,

    @JsonProperty("Confidence")
    @Schema(description = "Percentage of the chance that the label is in the project", example = "99.99")
    val confidence: Float?
)

When i send request GET /springwolf/docs i get response -

"com.example.Request": {
        "discriminator": "type",
        "title": "Request",
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "some description",
            "enum": [
              "REQUEST_1",
              "REQUEST_2"
            ],
            "examples": [
              "REQUEST_2"
            ]
          }
        },
        "description": "Base class for request",
        "examples": [
          {
            "jobId": "00000000-0000-0000-0000-000000000000",
            "objectId": "00000000-0000-0000-0000-000000000000",
            "type": "REQUEST_1"
          }
        ],
        "required": [
          "type"
        ],
        "oneOf": [
          {
            "$ref": "#/components/schemas/com.example.request.LabelDetectVideoRequest"
          },
          {
            "$ref": "#/components/schemas/com.example.request.LabelDetectImageRequest"
          }
        ]
      }

When i use springwolf-ui i see this exception
image
I debugging js code your springwolf-ui and found this code

private mapPayload(
payloadName: string,
schema: { $ref: string } | ServerAsyncApiSchema
): Message["payload"] {
if ("$ref" in schema) {
const payloadName = this.resolveRefId(schema.$ref);
return {
ts_type: "ref",
name: payloadName,
title: this.resolveTitleFromName(payloadName),
anchorUrl: AsyncApiMapperService.BASE_URL + payloadName,
};
}
return this.mapSchemaObj(payloadName, schema, {});
}

In this code method mapSchemaObj use parameter schemas how {} and in this method has this code

if (schema.oneOf !== undefined && schema.oneOf.length > 0) {
this.addPropertiesToSchema(schema.oneOf[0], properties, schemas);
}

In method addPropertiesToSchema this property use for get schemas for payload message

private addPropertiesToSchema(
schema: ServerAsyncApiSchemaOrRef,
properties: { [key: string]: any },
schemas: ServerComponents["schemas"]
) {
let actualSchema = this.resolveSchema(schema, schemas);
if ("properties" in actualSchema && actualSchema.properties !== undefined) {
Object.entries(actualSchema.properties).forEach(([key, value]) => {
properties[key] = this.mapSchema(key, value, schemas);
});
}
}

This method throw exception.

private resolveSchema(
schema: ServerAsyncApiSchemaOrRef,
schemas: ServerComponents["schemas"]
): ServerAsyncApiSchema {
let actualSchema = schema;
while ("$ref" in actualSchema) {
const refId = this.resolveRefId(actualSchema.$ref);
const refSchema = schemas[refId];
if (refSchema !== undefined) {
actualSchema = refSchema;
} else {
throw new Error("Schema " + refId + " not found");
}
}
return actualSchema;
}

Сode try get schema from {} object which was delivered

return this.mapSchemaObj(payloadName, schema, {});

Maybe I'm doing something wrong, but it looks like a bug.

@FanatTokyoGhoul FanatTokyoGhoul added the bug Something isn't working label Dec 18, 2024
Copy link

Welcome to Springwolf. Thanks a lot for reporting your first issue. Please check out our contributors guide and feel free to join us on discord.

@sam0r040
Copy link
Collaborator

Hi @FanatTokyoGhoul, thanks a lot for the detailed report. Any chance that you could provide the full specification so that we can reproduce the error with the specification only? You can also provide a MR that reproduces the error if thats easier for you.

@FanatTokyoGhoul
Copy link
Author

FanatTokyoGhoul commented Dec 24, 2024

Hi @sam0r040, sorry for the long reply. Here is the demo specification that causes this error.

{
  "asyncapi": "3.0.0",
  "info": {
    "title": "demo",
    "version": "1.0.0",
    "description": "Springwolf example project to demonstrate springwolfs abilities",
    "termsOfService": "http://asyncapi.org/terms",
    "contact": {
      "name": "springwolf",
      "url": "https://github.com/springwolf/springwolf-core",
      "email": "example@example.com"
    },
    "license": {
      "name": "Apache License 2.0"
    },
    "x-generator": "springwolf"
  },
  "defaultContentType": "application/json",
  "servers": {
    "sqs-server": {
      "host": "http://localhost:4566",
      "protocol": "sqs"
    }
  },
  "channels": {
    "queue": {
      "address": "queue",
      "messages": {
        "com.example.request.BaseRequest": {
          "$ref": "#/components/messages/com.example.request.BaseRequest"
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Request id header": {
        "title": "Request id header",
        "type": "object",
        "properties": {
          "request-id": {
            "type": "string",
            "description": "Generate from sqs sender",
            "enum": [ ]
          }
        },
        "examples": [
          {
            "request-id": "string"
          }
        ]
      },
      "com.example.demo.Request": {
        "discriminator": "type",
        "title": "Request",
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          }
        },
        "description": "Base class for request",
        "examples": [
          {
            "jobId": "00000000-0000-0000-0000-000000000000",
            "objectId": "00000000-0000-0000-0000-000000000000",
            "type": "string"
          }
        ],
        "required": [
          "type"
        ],
        "oneOf": [
          {
            "$ref": "#/components/schemas/com.example.demo.Request1"
          },
          {
            "$ref": "#/components/schemas/com.example.demo.Request2"
          }
        ]
      },
      "com.example.demo.Request1": {
        "type": "object",
        "description": "Class for request 1",
        "examples": [
          {
            "jobId": "00000000-0000-0000-0000-000000000000",
            "objectId": "00000000-0000-0000-0000-000000000000",
            "type": "string"
          }
        ],
        "required": [
          "jobId",
          "objectId"
        ],
        "allOf": [
          {
            "$ref": "#/components/schemas/com.example.demo.Request"
          },
          {
            "type": "object",
            "properties": {
              "jobId": {
                "type": "string",
                "description": "Job id from rekognition",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              },
              "objectId": {
                "type": "string",
                "description": "UUID of project",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              }
            }
          }
        ]
      },
      "com.example.demo.Request2": {
        "type": "object",
        "examples": [
          {
            "moderationLabels": [
              {
                "Confidence": 99.99,
                "Name": "Human"
              }
            ],
            "objectId": "00000000-0000-0000-0000-000000000000",
            "type": "string"
          }
        ],
        "required": [
          "moderationLabels",
          "objectId"
        ],
        "allOf": [
          {
            "$ref": "#/components/schemas/com.example.demo.Request"
          },
          {
            "type": "object",
            "properties": {
              "moderationLabels": {
                "type": "array",
                "description": "List labels from rekognition",
                "items": {
                  "$ref": "#/components/schemas/com.example.demo.RequestLabel"
                }
              },
              "objectId": {
                "type": "string",
                "description": "UUID of project",
                "examples": [
                  "00000000-0000-0000-0000-000000000000"
                ]
              }
            }
          }
        ]
      },
      "com.example.demo.RequestLabel": {
        "title": "RequestLabel",
        "type": "object",
        "properties": {
          "Confidence": {
            "type": "number",
            "description": "Percentage of the chance that the label is in the project",
            "format": "float",
            "examples": [
              99.99
            ]
          },
          "Name": {
            "type": "string",
            "description": "Label name",
            "examples": [
              "Human"
            ]
          }
        },
        "description": "Class for label description from rekognition",
        "examples": [
          {
            "Confidence": 99.99,
            "Name": "Human"
          }
        ]
      }
    },
    "messages": {
      "com.example.request.BaseRequest": {
        "headers": {
          "$ref": "#/components/schemas/Request id header"
        },
        "payload": {
          "schemaFormat": "application/vnd.aai.asyncapi+json;version=3.0.0",
          "schema": {
            "description": "Base class for request",
            "oneOf": [
              {
                "$ref": "#/components/schemas/com.example.demo.Request1"
              },
              {
                "$ref": "#/components/schemas/com.example.demo.Request2"
              }
            ]
          }
        },
        "name": "com.example.demo.Request",
        "title": "Request",
        "description": "Base class for request",
        "bindings": {
          "sqs": { }
        }
      }
    }
  },
  "operations": {
    "queue_receive_onMessage": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/queue"
      },
      "title": "queue_receive",
      "description": "some description",
      "bindings": {
        "sqs": {
          "queues": [
            {
              "name": "${amazon.sqs.queue}",
              "fifoQueue": true,
              "deliveryDelay": 0,
              "visibilityTimeout": 30,
              "receiveMessageWaitTime": 0,
              "messageRetentionPeriod": 345600
            }
          ],
          "bindingVersion": "0.2.0"
        }
      },
      "messages": [
        {
          "$ref": "#/channels/queue/messages/com.example.request.BaseRequest"
        }
      ]
    }
  }
}

I got this specification by calling /springwolf/docs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants