Skip to content

Validation issue #28

Closed
Closed

Description

@bielu

Hey @VisualBean,
I think i found issue with validation, my schema looks like that:

{
  "asyncapi": "3.1.0",
  "info": {
    "title": "Test API",
    "version": "1.0.0",
    "description": "Test API for integration tests"
  },
  "id": "urn:asyncapi",
  "servers": {
    "test-server": {
      "host": "localhost:5000",
      "protocol": "http"
    },
    "smtp-server": {
      "host": "localhost:25",
      "protocol": "smtp"
    },
    "websocket-server": {
      "host": "localhost:8080",
      "protocol": "ws"
    }
  },
  "channels": {
    "orderscreated": {
      "address": "orders/created",
      "messages": {
        "orderCreatedEvent": {
          "$ref": "#/components/messages/orderCreatedEvent"
        }
      },
      "description": "Channel for order creation events"
    },
    "orderscancelled": {
      "address": "orders/cancelled",
      "messages": {
        "orderCancelledEvent": {
          "$ref": "#/components/messages/orderCancelledEvent"
        }
      },
      "description": "Channel for order cancellation events"
    },
    "paymentsprocessed": {
      "address": "payments/processed",
      "messages": {
        "paymentProcessedEvent": {
          "$ref": "#/components/messages/paymentProcessedEvent"
        }
      },
      "description": "Channel for payment events"
    },
    "notificationsemail": {
      "address": "notifications/email",
      "messages": {
        "emailNotification": {
          "$ref": "#/components/messages/emailNotification"
        }
      },
      "description": "Email notification channel",
      "servers": [
        {
          "$ref": "#/servers/smtp-server"
        }
      ]
    },
    "notificationspush": {
      "address": "notifications/push",
      "messages": {
        "pushNotification": {
          "$ref": "#/components/messages/pushNotification"
        }
      },
      "description": "Push notification channel",
      "servers": [
        {
          "$ref": "#/servers/websocket-server"
        }
      ]
    },
    "usersuserIdevents": {
      "address": "users/{userId}/events",
      "messages": {
        "userUpdatedEvent": {
          "$ref": "#/components/messages/userUpdatedEvent"
        }
      },
      "description": "User-specific events channel",
      "parameters": {
        "userId": {
          "description": "The user identifier"
        }
      }
    },
    "inventoryupdates": {
      "address": "inventory/updates",
      "messages": {
        "inventoryUpdatedEvent": {
          "$ref": "#/components/messages/inventoryUpdatedEvent"
        }
      },
      "description": "Inventory update events"
    },
    "auditevents": {
      "address": "audit/events",
      "messages": {
        "auditLoginEvent": {
          "$ref": "#/components/messages/auditLoginEvent"
        },
        "auditLogoutEvent": {
          "$ref": "#/components/messages/auditLogoutEvent"
        },
        "auditActionEvent": {
          "$ref": "#/components/messages/auditActionEvent"
        }
      },
      "description": "Audit event channel"
    }
  },
  "operations": {
    "OrderEventHandler_HandleOrderCreated_Subscribe": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/orderscreated"
      },
      "title": "OrderEventHandler_HandleOrderCreated_Subscribe",
      "summary": "Subscribe to order creation events",
      "messages": [
        {
          "$ref": "#/channels/orderscreated/messages/orderCreatedEvent"
        }
      ]
    },
    "OrderEventHandler_HandleOrderCancelled_Subscribe": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/orderscancelled"
      },
      "title": "OrderEventHandler_HandleOrderCancelled_Subscribe",
      "summary": "Subscribe to order cancellation events",
      "messages": [
        {
          "$ref": "#/channels/orderscancelled/messages/orderCancelledEvent"
        }
      ]
    },
    "OrderEventPublisher_PublishOrderCreated_Publish": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/orderscreated"
      },
      "title": "OrderEventPublisher_PublishOrderCreated_Publish",
      "summary": "Publish order creation events",
      "messages": [
        {
          "$ref": "#/channels/orderscreated/messages/orderCreatedEvent"
        }
      ]
    },
    "handlePaymentProcessed": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/paymentsprocessed"
      },
      "title": "handlePaymentProcessed",
      "summary": "Handle payment processed events",
      "messages": [
        {
          "$ref": "#/channels/paymentsprocessed/messages/paymentProcessedEvent"
        }
      ]
    },
    "MultiServerHandler_SendEmailNotification_Publish": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/notificationsemail"
      },
      "title": "MultiServerHandler_SendEmailNotification_Publish",
      "summary": "Send email notifications",
      "messages": [
        {
          "$ref": "#/channels/notificationsemail/messages/emailNotification"
        }
      ]
    },
    "MultiServerHandler_SendPushNotification_Publish": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/notificationspush"
      },
      "title": "MultiServerHandler_SendPushNotification_Publish",
      "summary": "Send push notifications",
      "messages": [
        {
          "$ref": "#/channels/notificationspush/messages/pushNotification"
        }
      ]
    },
    "handleUserEvent": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/usersuserIdevents"
      },
      "title": "handleUserEvent",
      "summary": "Handle user-specific events",
      "messages": [
        {
          "$ref": "#/channels/usersuserIdevents/messages/userUpdatedEvent"
        }
      ]
    },
    "IInventoryEventHandler_HandleInventoryUpdate_Subscribe": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/inventoryupdates"
      },
      "title": "IInventoryEventHandler_HandleInventoryUpdate_Subscribe",
      "summary": "Handle inventory updates",
      "messages": [
        {
          "$ref": "#/channels/inventoryupdates/messages/inventoryUpdatedEvent"
        }
      ]
    },
    "handleAuditEvents": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/auditevents"
      },
      "title": "handleAuditEvents",
      "summary": "Handle all audit events",
      "messages": [
        {
          "$ref": "#/channels/auditevents/messages/auditLoginEvent"
        },
        {
          "$ref": "#/channels/auditevents/messages/auditLogoutEvent"
        },
        {
          "$ref": "#/channels/auditevents/messages/auditActionEvent"
        }
      ]
    }
  },
  "components": {
    "schemas": {
      "auditActionEvent": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string"
          },
          "action": {
            "type": "string"
          },
          "resource": {
            "type": "string"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "auditLoginEvent": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string"
          },
          "loginTime": {
            "type": "string",
            "format": "date-time"
          },
          "ipAddress": {
            "type": "string"
          }
        }
      },
      "auditLogoutEvent": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string"
          },
          "logoutTime": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "emailNotification": {
        "type": "object",
        "properties": {
          "to": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "body": {
            "type": "string"
          }
        }
      },
      "inventoryUpdatedEvent": {
        "type": "object",
        "properties": {
          "productId": {
            "type": "string"
          },
          "previousQuantity": {
            "type": "string",
            "format": "int32"
          },
          "newQuantity": {
            "type": "string",
            "format": "int32"
          },
          "warehouseId": {
            "type": "string"
          }
        }
      },
      "orderCancelledEvent": {
        "type": "object",
        "properties": {
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "reason": {
            "type": "string"
          },
          "cancelledAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "orderCreatedEvent": {
        "type": "object",
        "properties": {
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "customerName": {
            "type": "string"
          },
          "totalAmount": {
            "type": "string",
            "format": "double"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "productName": {
                  "type": "string"
                },
                "quantity": {
                  "type": "string",
                  "format": "int32"
                },
                "unitPrice": {
                  "type": "string",
                  "format": "double"
                }
              }
            }
          }
        }
      },
      "paymentProcessedEvent": {
        "type": "object",
        "properties": {
          "paymentId": {
            "type": "string",
            "format": "uuid"
          },
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "amount": {
            "type": "string",
            "format": "double"
          },
          "paymentMethod": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          }
        }
      },
      "pushNotification": {
        "type": "object",
        "properties": {
          "deviceToken": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "userUpdatedEvent": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    },
    "messages": {
      "orderCreatedEvent": {
        "payload": {
          "$ref": "#/components/schemas/orderCreatedEvent"
        },
        "name": "orderCreatedEvent",
        "title": "orderCreatedEvent"
      },
      "orderCancelledEvent": {
        "payload": {
          "$ref": "#/components/schemas/orderCancelledEvent"
        },
        "name": "orderCancelledEvent",
        "title": "orderCancelledEvent"
      },
      "paymentProcessedEvent": {
        "payload": {
          "$ref": "#/components/schemas/paymentProcessedEvent"
        },
        "name": "paymentProcessedEvent",
        "title": "paymentProcessedEvent"
      },
      "emailNotification": {
        "payload": {
          "$ref": "#/components/schemas/emailNotification"
        },
        "name": "emailNotification",
        "title": "emailNotification"
      },
      "pushNotification": {
        "payload": {
          "$ref": "#/components/schemas/pushNotification"
        },
        "name": "pushNotification",
        "title": "pushNotification"
      },
      "userUpdatedEvent": {
        "payload": {
          "$ref": "#/components/schemas/userUpdatedEvent"
        },
        "name": "userUpdatedEvent",
        "title": "userUpdatedEvent"
      },
      "inventoryUpdatedEvent": {
        "payload": {
          "$ref": "#/components/schemas/inventoryUpdatedEvent"
        },
        "name": "inventoryUpdatedEvent",
        "title": "inventoryUpdatedEvent"
      },
      "auditLoginEvent": {
        "payload": {
          "$ref": "#/components/schemas/auditLoginEvent"
        },
        "name": "auditLoginEvent",
        "title": "auditLoginEvent"
      },
      "auditLogoutEvent": {
        "payload": {
          "$ref": "#/components/schemas/auditLogoutEvent"
        },
        "name": "auditLogoutEvent",
        "title": "auditLogoutEvent"
      },
      "auditActionEvent": {
        "payload": {
          "$ref": "#/components/schemas/auditActionEvent"
        },
        "name": "auditActionEvent",
        "title": "auditActionEvent"
      }
    }
  }
}

however when i debugged validation for servers:

Image it seems it applied v2 validation instead of v3 because of usage
  // Assert - Parse with ByteBard.AsyncAPI.NET reader to validate schema
        var reader = new AsyncApiStringReader();
        var document = reader.Read(content, out var diagnostic);

You can find example in this test:
https://github.com/bielu/Bielu.AspNetCore.AsyncApi/blob/a60eb5f1a0ed645137f0f831ae9f17b9ee25d257/test/Bielu.AspNetCore.AsyncApi.Tests/Integration/AsyncApiDocumentGenerationTests.cs#L135

Activity

  1. VisualBean commented on Feb 24, 2026

    @VisualBean
    Contributor

    Im not sure I understand the issue. You are saying double parsing and v2 validation.

    What is your expected outcome?
    Do you have a 'minimal' repro?

    Edit: I think i see it now. The diagnostics errors are wrong.

  2. VisualBean commented on Feb 24, 2026

    @VisualBean
    Contributor

    TLDR;
    They are parsed twice, due to the references.
    There is a missing registration step in the AsyncApiWorkspace (we do similar for channels)

    Add something like

    string serverBaseUri = "#/servers/";
                foreach (var server in document.Servers)
                {
                    var registerableServerValue = server.Value;
                    if (server.Value is IAsyncApiReferenceable reference)
                    {
                        if (reference.Reference.IsExternal)
                        {
                            continue;
                        }
    
                        registerableServerValue = this.ResolveReference<AsyncApiServer>(reference.Reference);
                    }
    
                    location = serverBaseUri + server.Key;
                    this.RegisterComponent(location, registerableServerValue);
                }

    below where channels are registered in AsyncApiWorkspace (line 207)
    This will register them, so we don't instantiate them twice (as part of the reference resolution).

    Currently we deserialize the spec, then walk the references to properly dereference them.
    Most references will already have been pre-registered in the AsyncApiWorkspace, as part of the initial deserialization, but servers were missing, so they were looked up and instantiated (why they were parsed multiple times).

  3. VisualBean commented on Feb 25, 2026

    @VisualBean
    Contributor

    #29 fixes this issue. closing.

  4. bielu commented on Feb 25, 2026

    @bielu
    ContributorAuthor

    @VisualBean can ping me when you release next beta?

  5. VisualBean commented on Feb 25, 2026

    @VisualBean
    Contributor

    @bielu ByteBard.AsyncAPI.NET.2.1.3-beta.14 has been released

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions