Skip to content

Commit

Permalink
Update to AsyncApi 3.0 (#507)
Browse files Browse the repository at this point in the history
* feat(ui): prepare springwolf for AsyncApi 3.0 (wip)

feat(ui): remove testing code

feat(ui): remove asyncApi 2.0 hint about publish/subscribe

feat(ui): add parsing error boundary

feat(ui): add license chip

feat(ui): add notification.service.ts

feat(ui): rename Channel to ChannelOperation

also fix bindings

feat(ui): update asyncapi mapper (wip)

feat(ui): add todos

feat(ui): update asyncapi models to v3

chore(ui): split model packages

chore(ui): download asyncapi document only once

refactor(ui): re-organize components

chore(ui): use asyncapi v3 mock

chore(ui): remove IE support

chore(ui): move favicon.ico to assets

* feat(asyncapi): update package description

* feat(asyncapi): add todos

* Enhancement: Add More Spec Details to the UI (#517)

* test: ignore line ending in generated specs assertions

Is failing in Windows OS

Refs #378

* refactor(kafka): add primitive payload, example of `minimum` value

To visualize in UI

Refs #378

test(kafka): add primitive types

More data to demonstrate + type badges check

* refactor(ui): add schema properties template

To reuse for primitive types

Refs #378

* feat(ui): add `type` badge to schema payload header

 No intuitive way to check whether the payload is an objects or a string

Refs #378

* feat(ui): add minimum and maximum values to the spec`s model

Feature request

Refs #378

* feat(ui): display primitive type info

Feature request.
Only properties has info displayed

Refs #378

* feat(ui): display minimum and maximum values

Feature request.

Refs #378

* Revert "test: ignore line ending in generated specs assertions"

This reverts commit 07a6b30.

* feat: Replaced usage of jAsyncAPI by the new springwolf-asyncapi module (#523)

* feat: Replaced usage of jAsyncAPI by the new springwolf-asyncapi module

We migrate from jAsyncAPI, with support for AsyncAPI v2, to the new SpringWolf-AsyncAPI module, which supports AsyncAPI v3.

This PR is only a first step. Plenty of tests are still not working and others are partially commented.

Please take a look to the multiple FIXMEs in the code

* Replaced Springwolf-Core Message related classes by their Springwolf-AsyncAPI equivalent. (#527)

Fixes some FIXMEs and removes duplicated classes

* AsyncAPI v3 Migration Fixes (#533)

* fix: Multiple fixes related with Channel Messages

Some of the tests are failing due to some AssertJ recursion issue. Help is welcome

* fix: Minor properties fixes

* fix: Fixes to AMQP

Some fixes to the AMQP plugin and example (Still not finished)

* fix: Minor properties fixes

* fix: Migrated asyncapi.json examples to v3

* fix: Migration to AsyncAPI v3

As per https://www.asyncapi.com/docs/migration/migrating-to-v3

For a channel with multiple messages, you specify multiple key-value pairs. For a channel with just one message, you use a single key-value pair.

That means that the use of 'oneOf' is removed.

* fix: Improved support for Channel Messages

Fixed different bugs and limitations with the publishing of Messages in a Channel with the AsyncAPI v3 spec

* fix: Fixed Channel Merger

When merging Channels, the messages were not merged. This is now fixed.

* fix: Added basic support for Operations

Still plenty of errors, but Operations are now appearing in the AsyncAPI v3 output.

* fix: AsyncAPI module test

* Feat/asyncapi v3 UI fixes (#539)

* fix: Added missing comparison code

Those missing annotations were causing some
tests to fail

* fix: AMQP operation binding

As per documentation, 'deliveryMode' can only be 1 or 2. The default value 0 was incorrect

* fix: Added Channel Ref to Operation

* test: update asyncapi.json files based on current state in the branch

Generate asyncapi.actual.json files by running in _springwolf_examples_ folder:

`../gradlew test --tests "**.ApiIntegrationTest" --continue`

* test(kafka): update asyncapi model

* feat(ui): move asyncapi v3 changes

---------

Co-authored-by: Carlos Tasada <[email protected]>

* fix: Fixed core tests (#540)

* fix: Fixed core tests

Fixed the failing tests after migrating to AsyncAPI v3

* fix: Fixed AMQP integration

The AMQP was adding both 'exchange' & 'queue' keys, but it should only include the
one related with the binding type.

Also, deliveryMode can be ONLY 1 or 2, the
default value 0 which was used in different places is wrong.

* fix: Fixed AMQP integration

The AMQP was adding both 'exchange' & 'queue' keys, but it should only include the
one related with the binding type.

Also, deliveryMode can be ONLY 1 or 2, the
default value 0 which was used in different places is wrong.

* Feat/message as component (#541)

* feat: move messages to components

* feat: add ChannelMessageReference

allows to link from an operation to the message within a specific channel

* chore: update asyncapi.json

* feat(ui): update after message was moved to components

* fix(ui): map operation binding correctly

* feat(core): remove ChannelMessageReference

* test(example): simplify creation of asyncapi.json files

* test(asyncapi): change channel messages type back to Message

* refactor(asyncapi): rename MessageReference static message

Better indicate what the reference points to

* chore: spotless

* test: fix DefaultChannelsServiceIntegrationTest

* test(kafka): remove duplicated primitite-topic consumer

* feat(core): use AsyncApi 3.0 OperationAction

* feat(ui): show contact info

* chore: update dependency list

* Different fixes in the asyncapi v3 spec examples (#543)

* fix: Minor fix to core spec example

The asyncapi test sample had a minor issue when validated with https://studio.asyncapi.com/

* fix: Minor fix to sns-plugin spec example

The asyncapi test sample had a minor issue when validated with https://studio.asyncapi.com/

We need to properly configure those values, something that I will take care of in the next PR

* fix: Minor fix to sqs-plugin spec example

The asyncapi test sample had a minor issue when validated with https://studio.asyncapi.com/

We need to properly configure those values, something that I will take care of in the next PR

* Fixed styles

* fix: Fixed broken tests

* fix: Added support to sqs-plugin to generate the minimum needed values

To pass the SQS Binding AsyncAPI validation, we need to provide some required fields. This commit adds the minimum values needed.

* fix: Added support to sns-plugin to generate the minimum needed values

To pass the SNS Binding AsyncAPI validation, we need to provide some required fields. This commit adds the minimum values needed.

* fix: Removed small batch of pending FIXMEs

* fix: Removed small batch of pending FIXMEs

* fix: Removed small batch of pending FIXMEs

* fix: Fixed the cloudstream-plugin

The cloudstream-plugin was not generating the operations section of the AsyncAPI spec. Now is fixed and validated.

Multiple FIXMEs were fixed and removed too

* fix: Removed unused methods

* fix: Fixed CloudStream operation action typo

* fix: Removed unneeded channelId field from ChannelObject

* fix: Fixed some code analysis complains

* fix: Fixed Operation messages bug

Whe configuring annotations on the class level, the generated Operations didn't have the proper message references.

This fixes the latest blocking FIXME

* fix: Schema Properties

The Schema Properties should support any kind of value, like booleans.

Making the property accept `Object` seems a more flexible approach.

* Feat/asyncapi 3: more fixes (#550)

* chore: typo

* fix: correct Listener=receive, Publisher=send operationAction

* feat(kafka): handle kafka message binding key

* chore: update example json

* feat(core): all operation include the action in the id

refactor: rename operationName to operationId

* feat(cloud-stream): align asyncapi.json structure

* feat(ui): show message when bindings are missing

* chore: resolve open FIXME

* chore: fix dependencies

* Support exclusiveMinimum and exclusiveMaximum (#560)

* feat(ui): add exclusiveMinimum and exclusiveMaximum values to the spec`s model
Feature request

Refs #378

* refactor(ui): reimplement range temple model

For further extension

Refs #378

* feat(ui): add support for exclusiveMinimum and exclusiveMaximum

Feature request

Refs #378

* refactor(ui): separate schema range as a component

For further extensions and maintenance

Refs #378

* test: add exclusiveMinimum to example Dto

To test changes visually

Refs #378

* chore: Split ChannelScanner and OperationScanner (#562)

---------

Co-authored-by: Nikita Marunko <[email protected]>
Co-authored-by: Carlos Tasada <[email protected]>
  • Loading branch information
3 people authored Jan 26, 2024
1 parent 7a0f8dc commit 02de447
Show file tree
Hide file tree
Showing 271 changed files with 6,103 additions and 3,163 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class MonetaryAmount {

@JsonProperty("amount")
@Schema(example = "99.99")
@Schema(example = "99.99", minimum = "0.1", exclusiveMinimum = true)
private BigDecimal amount;

@JsonProperty("currency")
Expand Down
4 changes: 3 additions & 1 deletion springwolf-add-ons/springwolf-generic-binding/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ plugins {

dependencies {
api project(":springwolf-core")
api project(":springwolf-asyncapi")

implementation "com.asyncapi:asyncapi-core:${asyncapiCoreVersion}"
implementation "org.slf4j:slf4j-api:${slf4jApiVersion}"

implementation "org.springframework:spring-context"
implementation "org.springframework:spring-core"

annotationProcessor "org.projectlombok:lombok:${lombokVersion}"

compileOnly "org.projectlombok:lombok:${lombokVersion}"

testImplementation "org.assertj:assertj-core:${assertjCoreVersion}"
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.stavshamir.springwolf.addons.generic_binding.annotation.processor;

import com.asyncapi.v2.binding.operation.OperationBinding;
import io.github.stavshamir.springwolf.addons.generic_binding.annotation.AsyncGenericOperationBinding;
import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.ProcessedOperationBinding;
import io.github.stavshamir.springwolf.asyncapi.scanners.bindings.processor.AbstractOperationBindingProcessor;
import io.github.stavshamir.springwolf.asyncapi.v3.bindings.OperationBinding;

import java.util.HashMap;
import java.util.Map;
Expand Down
1 change: 1 addition & 0 deletions springwolf-add-ons/springwolf-json-schema/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
implementation "org.springframework:spring-context"

annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
compileOnly "org.projectlombok:lombok:${lombokVersion}"

testImplementation "org.mockito:mockito-core:${mockitoCoreVersion}"
testImplementation "org.assertj:assertj-core:${assertjCoreVersion}"
Expand Down
1 change: 1 addition & 0 deletions springwolf-asyncapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ dependencies {

See https://github.com/asyncapi/converter-js#conversion-2xx-to-3xx


# Contributions

Any contributions are welcome, including, but not limited to:
Expand Down
2 changes: 1 addition & 1 deletion springwolf-asyncapi/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ publishing {
mavenJava(MavenPublication) {
pom {
name = 'springwolf-asyncapi'
description = 'AsyncAPI schema generator'
description = 'Springwolf implementation of the AsyncApi specification'
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class SQSChannelBindingQueue {
* identifier should be the one in this field.
*/
@NotNull
@JsonProperty("queue")
@JsonProperty("name")
private String name;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

import java.util.List;

/**
* SQS Point-To-Point
* </p>
Expand All @@ -32,7 +34,7 @@ public class SQSOperationBinding extends OperationBinding {
*/
@NotNull
@JsonProperty("queues")
private SQSChannelBindingQueue queues;
private List<SQSChannelBindingQueue> queues;

/**
* Optional, defaults to latest. The version of this binding.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.stavshamir.springwolf.asyncapi.v3.model.channel;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.github.stavshamir.springwolf.asyncapi.v3.bindings.ChannelBinding;
import io.github.stavshamir.springwolf.asyncapi.v3.model.ExtendableObject;
Expand Down Expand Up @@ -29,14 +28,6 @@
@EqualsAndHashCode(callSuper = true)
public class ChannelObject extends ExtendableObject implements Channel {

/**
* An identifier for the described channel. The channelId value is case-sensitive. Tools and libraries MAY use the
* channelId to uniquely identify a channel, therefore, it is RECOMMENDED to follow common programming naming
* conventions.
*/
@JsonIgnore
private String channelId;

/**
* An optional string representation of this channel's address. The address is typically the "topic name",
* "routing key", "event type", or "path". When null or absent, it MUST be interpreted as unknown. This is useful
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,4 @@ public String getRef() {
public static ChannelReference fromChannel(String channelName) {
return new ChannelReference("#/channels/" + channelName);
}

/**
* Convenient Builder to create a Channel reference to an existing Channel
* @param channel Channel to create the reference to. This Channel MUST have a 'channelId' field
* @return a Channel with the 'ref' field pointing to "#/channels/{channelId"
*/
public static ChannelReference fromChannel(ChannelObject channel) {
var channelId = channel.getChannelId();
if (channelId == null) {
throw new IllegalArgumentException("The channel must have a 'channelId' defined");
}
return new ChannelReference("#/channels/" + channelId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import io.github.stavshamir.springwolf.asyncapi.v3.jackson.model.channel.message.MessageHeadersSerializer;
import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.MultiFormatSchema;
import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.SchemaObject;
import lombok.EqualsAndHashCode;
import lombok.Getter;

@Getter
@JsonSerialize(using = MessageHeadersSerializer.class)
@EqualsAndHashCode
public class MessageHeaders {
private MultiFormatSchema multiFormatSchema;
private SchemaObject schema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,14 @@ public class MessageObject extends ExtendableObject implements Message {
*/
@JsonProperty(value = "traits")
private List<MessageTrait> traits;

/*
* Override the getMessageId to guarantee that there's always a value. Defaults to 'name'
*/
public String getMessageId() {
if (messageId == null) {
return this.name;
}
return messageId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import io.github.stavshamir.springwolf.asyncapi.v3.jackson.model.channel.message.MessagePayloadSerializer;
import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.MultiFormatSchema;
import io.github.stavshamir.springwolf.asyncapi.v3.model.schema.SchemaObject;
import lombok.EqualsAndHashCode;
import lombok.Getter;

@Getter
@JsonSerialize(using = MessagePayloadSerializer.class)
@EqualsAndHashCode
public class MessagePayload {
private MultiFormatSchema multiFormatSchema;
private SchemaObject schema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,27 @@ public String getRef() {

/**
* Convenient Builder to create a Message reference to an existing Message
* @param message Message to create the reference to. This Message MUST have a 'messageId' field
* @return a Message with the 'ref' field pointing to "#/components/messages/{messageId"
*
* @param message Message to create the reference to. This Message MUST have a 'messageName' field
* @return a Message with the 'ref' field pointing to "#/components/messages/{messageName}"
*/
public static MessageReference fromMessage(MessageObject message) {
var messageId = message.getMessageId();
if (messageId == null) {
throw new IllegalArgumentException("The message must have a 'messageId' defined");
}
return new MessageReference("#/components/messages/" + messageId);
public static MessageReference toComponentMessage(MessageObject message) {
return toComponentMessage(message.getName());
}

public static MessageReference fromMessage(String messageName) {
public static MessageReference toComponentMessage(String messageName) {
return new MessageReference("#/components/messages/" + messageName);
}

public static MessageReference fromSchema(String schemaName) {
public static MessageReference toChannelMessage(String channelName, MessageObject message) {
return new MessageReference("#/channels/" + channelName + "/messages/" + message.getName());
}

public static MessageReference toChannelMessage(String channelName, String messageName) {
return new MessageReference("#/channels/" + channelName + "/messages/" + messageName);
}

public static MessageReference toSchema(String schemaName) {
return new MessageReference("#/components/schemas/" + schemaName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class SchemaObject extends ExtendableObject implements Schema {
private String type;

@JsonProperty(value = "properties")
private Map<String, Schema> properties;
private Map<String, Object> properties;

/**
* <a href="https://spec.commonmark.org/">CommonMark syntax</a> can be used for rich text representation.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.stavshamir.springwolf.asyncapi.v3.model.schema;

public class SchemaType {
public static final String NULL = "null";
public static final String BOOLEAN = "boolean";
public static final String OBJECT = "object";
public static final String ARRAY = "array";
public static final String NUMBER = "number";
public static final String STRING = "string";
public static final String INTEGER = "integer";

private SchemaType() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class AMQPBindingTest {
private static final DefaultAsyncApiSerializer serializer = new DefaultAsyncApiSerializer();

@Test
void shouldSerializeAMQPChannelBinding() throws IOException {
void shouldSerializeAMQPChannelBindingQueue() throws IOException {

var asyncapi = AsyncAPI.builder()
.channels(Map.of(
Expand All @@ -30,13 +30,36 @@ void shouldSerializeAMQPChannelBinding() throws IOException {
.bindings(Map.of(
"amqp",
AMQPChannelBinding.builder()
.is(AMQPChannelType.ROUTING_KEY)
.is(AMQPChannelType.QUEUE)
.queue(AMQPChannelQueueProperties.builder()
.name("my-queue-name")
.durable(true)
.exclusive(true)
.autoDelete(false)
.build())
.build()))
.build()))
.build();

// Uses https://github.com/asyncapi/bindings/blob/master/amqp/README.md#example
var example = ClasspathUtil.parseYamlFile("/v3/bindings/amqp/amqp-channel-queue.yaml");
assertThatJson(serializer.toJsonString(asyncapi))
.whenIgnoringPaths("asyncapi", "operations")
.isEqualTo(example);
}

@Test
void shouldSerializeAMQPChannelBindingRouting() throws IOException {

var asyncapi = AsyncAPI.builder()
.channels(Map.of(
"userSignup",
ChannelObject.builder()
.address("user/signup")
.bindings(Map.of(
"amqp",
AMQPChannelBinding.builder()
.is(AMQPChannelType.ROUTING_KEY)
.exchange(AMQPChannelExchangeProperties.builder()
.name("myExchange")
.type(AMQPChannelExchangeType.TOPIC)
Expand All @@ -48,7 +71,7 @@ void shouldSerializeAMQPChannelBinding() throws IOException {
.build();

// Uses https://github.com/asyncapi/bindings/blob/master/amqp/README.md#example
var example = ClasspathUtil.parseYamlFile("/v3/bindings/amqp/amqp-channel.yaml");
var example = ClasspathUtil.parseYamlFile("/v3/bindings/amqp/amqp-channel-routing.yaml");
assertThatJson(serializer.toJsonString(asyncapi))
.whenIgnoringPaths("asyncapi", "operations")
.isEqualTo(example);
Expand Down
Loading

0 comments on commit 02de447

Please sign in to comment.