feat: add asyncapi 3.0 (#1620)

This commit is contained in:
Alex Varchuk
2024-07-24 11:29:24 +03:00
committed by GitHub
parent 9c3263266f
commit 48f1401844
60 changed files with 4277 additions and 53 deletions

View File

@@ -0,0 +1,6 @@
---
"@redocly/openapi-core": minor
"@redocly/cli": minor
---
Added support for AsyncAPI 3.0 description linting.

View File

@@ -0,0 +1,11 @@
UserSignedUp:
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user

View File

@@ -0,0 +1,3 @@
apis:
main:
root: ./simple.yml

View File

@@ -0,0 +1,22 @@
asyncapi: 3.0.0
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user signups
channels:
userSignedup:
address: user/signedup
messages:
UserSignedUp:
$ref: '#/components/messages/UserSignedUp'
operations:
sendUserSignedup:
action: send
channel:
$ref: '#/channels/userSignedup'
messages:
- $ref: '#/channels/userSignedup/messages/UserSignedUp'
components:
messages:
UserSignedUp:
$ref: ./UserSignedUp.yaml

View File

@@ -0,0 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`E2E bundle async3 1`] = `
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user signups
components:
messages:
UserSignedUp:
UserSignedUp:
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user
asyncapi: 3.0.0
channels:
userSignedup:
address: user/signedup
messages:
UserSignedUp:
UserSignedUp:
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user
operations:
sendUserSignedup:
action: send
channel:
$ref: '#/channels/userSignedup'
messages:
- UserSignedUp:
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user
bundling ./simple.yml...
📦 Created a bundle for ./simple.yml at stdout <test>ms.
`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,36 @@
asyncapi: 3.0.0
info:
title: AnyOf example
version: 1.0.0
channels:
test:
address: test
messages:
testMessages:
$ref: '#/components/messages/testMessages'
operations:
test:
action: receive
channel:
$ref: '#/channels/test'
messages:
- $ref: '#/channels/test/messages/testMessages'
components:
messages:
testMessages:
payload:
anyOf:
- $ref: '#/components/schemas/objectWithKey'
- $ref: '#/components/schemas/objectWithKey2'
schemas:
objectWithKey:
type: object
properties:
key:
type: string
additionalProperties: false
objectWithKey2:
type: object
properties:
key2:
type: string

View File

@@ -0,0 +1,86 @@
asyncapi: 3.0.0
info:
title: Application Headers example
version: 1.0.0
description: A cut of the Streetlights API to test application header changes supporting
license:
name: Apache 2.0
url: 'https://www.apache.org/licenses/LICENSE-2.0'
defaultContentType: application/json
servers:
production:
host: 'test.mosquitto.org:{port}'
protocol: mqtt
description: Test broker
variables:
port:
description: Secure connection (TLS) is available through port 8883.
default: '1883'
enum:
- '1883'
- '8883'
channels:
lightingMeasured:
address: 'smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured'
messages:
lightMeasured:
$ref: '#/components/messages/lightMeasured'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
operations:
receiveLightMeasurement:
action: receive
channel:
$ref: '#/channels/lightingMeasured'
summary: >-
Inform about environmental lighting conditions of a particular
streetlight.
messages:
- $ref: '#/channels/lightingMeasured/messages/lightMeasured'
components:
messages:
lightMeasured:
name: lightMeasured
title: Light measured
summary: >-
Inform about environmental lighting conditions of a particular
streetlight.
correlationId:
location: $message.header#/MQMD/CorrelId
contentType: application/json
headers:
type: object
properties:
MQMD:
type: object
properties:
CorrelId:
type: string
minLength: 24
maxLength: 24
format: binary
applicationInstanceId:
$ref: '#/components/schemas/applicationInstanceId'
payload:
$ref: '#/components/schemas/lightMeasuredPayload'
schemas:
lightMeasuredPayload:
type: object
properties:
lumens:
type: integer
minimum: 0
description: Light intensity measured in lumens.
sentAt:
$ref: '#/components/schemas/sentAt'
sentAt:
type: string
format: date-time
description: Date and time when the message was sent.
applicationInstanceId:
description: Unique identifier for a given instance of the publishing application
type: string
parameters:
streetlightId:
description: The ID of the streetlight.

View File

@@ -0,0 +1,180 @@
asyncapi: 3.0.0
info:
title: Correlation ID Example
version: 1.0.0
description: A cut of the Streetlights API to test Correlation ID
license:
name: Apache 2.0
url: 'https://www.apache.org/licenses/LICENSE-2.0'
defaultContentType: application/json
servers:
production:
host: 'test.mosquitto.org:{port}'
protocol: mqtt
description: Test broker
variables:
port:
description: Secure connection (TLS) is available through port 8883.
default: '1883'
enum:
- '1883'
- '8883'
security:
- $ref: '#/components/securitySchemes/apiKey'
- type: oauth2
description: Flows to support OAuth 2.0
flows:
implicit:
authorizationUrl: 'https://authserver.example/auth'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
password:
tokenUrl: 'https://authserver.example/token'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
clientCredentials:
tokenUrl: 'https://authserver.example/token'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
authorizationCode:
authorizationUrl: 'https://authserver.example/auth'
tokenUrl: 'https://authserver.example/token'
refreshUrl: 'https://authserver.example/refresh'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
scopes:
- 'streetlights:on'
- 'streetlights:off'
- 'streetlights:dim'
- $ref: '#/components/securitySchemes/openIdConnectWellKnown'
channels:
lightingMeasured:
address: 'smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured'
messages:
lightMeasured:
$ref: '#/components/messages/lightMeasured'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
lightsDim:
address: 'smartylighting/streetlights/1/0/action/{streetlightId}/dim'
messages:
dimLight:
$ref: '#/components/messages/dimLight'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
operations:
receiveLightMeasurement:
action: receive
channel:
$ref: '#/channels/lightingMeasured'
summary: >-
Inform about environmental lighting conditions of a particular
streetlight.
messages:
- $ref: '#/channels/lightingMeasured/messages/lightMeasured'
dimLight:
action: send
channel:
$ref: '#/channels/lightsDim'
messages:
- $ref: '#/channels/lightsDim/messages/dimLight'
components:
messages:
lightMeasured:
name: lightMeasured
title: Light measured
summary: >-
Inform about environmental lighting conditions of a particular
streetlight.
correlationId:
location: $message.header#/MQMD/CorrelId
contentType: application/json
payload:
$ref: '#/components/schemas/lightMeasuredPayload'
dimLight:
name: dimLight
title: Dim light
summary: Command a particular streetlight to dim the lights.
correlationId:
$ref: '#/components/correlationIds/sentAtCorrelator'
payload:
$ref: '#/components/schemas/dimLightPayload'
schemas:
lightMeasuredPayload:
type: object
properties:
lumens:
type: integer
minimum: 0
description: Light intensity measured in lumens.
sentAt:
$ref: '#/components/schemas/sentAt'
dimLightPayload:
type: object
properties:
percentage:
type: integer
description: Percentage to which the light should be dimmed to.
minimum: 0
maximum: 100
sentAt:
$ref: '#/components/schemas/sentAt'
sentAt:
type: string
format: date-time
description: Date and time when the message was sent.
parameters:
streetlightId:
description: The ID of the streetlight.
correlationIds:
sentAtCorrelator:
description: Data from message payload used as correlation ID
location: $message.payload#/sentAt
securitySchemes:
apiKey:
type: apiKey
in: user
description: Provide your API key as the user and leave the password empty.
supportedOauthFlows:
type: oauth2
description: Flows to support OAuth 2.0
flows:
implicit:
authorizationUrl: 'https://authserver.example/auth'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
password:
tokenUrl: 'https://authserver.example/token'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
clientCredentials:
tokenUrl: 'https://authserver.example/token'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
authorizationCode:
authorizationUrl: 'https://authserver.example/auth'
tokenUrl: 'https://authserver.example/token'
refreshUrl: 'https://authserver.example/refresh'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
openIdConnectWellKnown:
type: openIdConnect
openIdConnectUrl: 'https://authserver.example/.well-known'

View File

@@ -0,0 +1,178 @@
asyncapi: 3.0.0
id: 'tag:stream.gitter.im,2022:api'
info:
title: Gitter Streaming API
version: 1.0.0
servers:
production:
host: stream.gitter.im
pathname: /v1
protocol: https
protocolVersion: '1.1'
security:
- $ref: '#/components/securitySchemes/httpBearerToken'
channels:
rooms:
address: '/rooms/{roomId}/{resource}'
messages:
chatMessage:
$ref: '#/components/messages/chatMessage'
heartbeat:
$ref: '#/components/messages/heartbeat'
parameters:
roomId:
description: Id of the Gitter room.
examples:
- 53307860c3599d1de448e19d
resource:
enum:
- chatMessages
- events
description: The resource to consume.
operations:
sendRoomInfo:
action: send
channel:
$ref: '#/channels/rooms'
bindings:
http:
method: POST
messages:
- $ref: '#/channels/rooms/messages/chatMessage'
- $ref: '#/channels/rooms/messages/heartbeat'
components:
securitySchemes:
httpBearerToken:
type: http
scheme: bearer
messages:
chatMessage:
summary: >-
A message represents an individual chat message sent to a room. They are
a sub-resource of a room.
payload:
schemaFormat: application/schema+yaml;version=draft-07
schema:
type: object
properties:
id:
type: string
description: ID of the message.
text:
type: string
description: Original message in plain-text/markdown.
html:
type: string
description: HTML formatted message.
sent:
type: string
format: date-time
description: ISO formatted date of the message.
fromUser:
type: object
description: User that sent the message.
properties:
id:
type: string
description: Gitter User ID.
username:
type: string
description: Gitter/GitHub username.
displayName:
type: string
description: Gitter/GitHub user real name.
url:
type: string
description: Path to the user on Gitter.
avatarUrl:
type: string
format: uri
description: User avatar URI.
avatarUrlSmall:
type: string
format: uri
description: User avatar URI (small).
avatarUrlMedium:
type: string
format: uri
description: User avatar URI (medium).
v:
type: number
description: Version.
gv:
type: string
description: Stands for "Gravatar version" and is used for cache busting.
unread:
type: boolean
description: Boolean that indicates if the current user has read the message.
readBy:
type: number
description: Number of users that have read the message.
urls:
type: array
description: List of URLs present in the message.
items:
type: string
format: uri
mentions:
type: array
description: List of @Mentions in the message.
items:
type: object
properties:
screenName:
type: string
userId:
type: string
userIds:
type: array
items:
type: string
issues:
type: array
description: 'List of #Issues referenced in the message.'
items:
type: object
properties:
number:
type: string
meta:
type: array
description: Metadata. This is currently not used for anything.
items: {}
v:
type: number
description: Version.
gv:
type: string
description: Stands for "Gravatar version" and is used for cache busting.
bindings:
http:
headers:
type: object
properties:
Transfer-Encoding:
type: string
const: chunked
Trailer:
type: string
const: \r\n
heartbeat:
summary: Its purpose is to keep the connection alive.
payload:
schemaFormat: application/schema+yaml;version=draft-07
schema:
type: string
enum:
- "\r\n"
bindings:
http:
headers:
type: object
properties:
Transfer-Encoding:
type: string
const: chunked
Trailer:
type: string
const: \r\n

View File

@@ -0,0 +1,58 @@
asyncapi: 3.0.0
info:
title: Mercure Hub Example
version: 1.0.0
description: This example demonstrates how to define a Mercure hub.
defaultContentType: application/ld+json
servers:
production:
host: demo.mercure.rocks
pathname: /.well-known/mercure
protocol: mercure
channels:
books:
address: 'https://example.com/books/{id}'
messages:
book:
$ref: '#/components/messages/book'
description: >-
Every time a resource of type `http://schema.org/Book` is created or
modified, a JSON-LD representation of the new version of this resource
must be pushed in this Mercure topic.
parameters:
id:
description: ID of the book
operations:
ReceiveBooksInfo:
action: receive
channel:
$ref: '#/channels/books'
messages:
- $ref: '#/channels/books/messages/book'
SendBooksInfo:
action: send
channel:
$ref: '#/channels/books'
messages:
- $ref: '#/channels/books/messages/book'
components:
messages:
book:
summary: The content of a book resource.
externalDocs:
url: 'https://schema.org/Book'
payload:
type: object
properties:
'@id':
type: string
format: iri-reference
'@type':
type: string
format: iri-reference
name:
type: string
isbn:
type: string
abstract:
type: string

View File

@@ -0,0 +1,29 @@
asyncapi: 3.0.0
info:
title: Not example
version: 1.0.0
channels:
test:
address: test
messages:
testMessages:
$ref: '#/components/messages/testMessages'
operations:
onTestMsg:
action: receive
channel:
$ref: '#/channels/test'
messages:
- $ref: '#/channels/test/messages/testMessages'
components:
messages:
testMessages:
payload:
$ref: '#/components/schemas/testSchema'
schemas:
testSchema:
type: object
properties:
key:
not:
type: integer

View File

@@ -0,0 +1,57 @@
asyncapi: 3.0.0
info:
title: OneOf example
version: 1.0.0
channels:
test:
address: test
messages:
testMessages:
$ref: '#/components/messages/testMessages'
test2:
address: test2
messages:
objectWithKey:
payload:
$ref: '#/components/schemas/objectWithKey'
objectWithKey2:
payload:
$ref: '#/components/schemas/objectWithKey2'
operations:
onTestMsg:
action: receive
channel:
$ref: '#/channels/test'
messages:
- $ref: '#/channels/test/messages/testMessages'
sendTest:
action: send
channel:
$ref: '#/channels/test2'
messages:
- $ref: '#/channels/test2/messages/objectWithKey'
- $ref: '#/channels/test2/messages/objectWithKey2'
components:
messages:
testMessages:
payload:
oneOf:
- $ref: '#/components/schemas/objectWithKey'
- $ref: '#/components/schemas/objectWithKey2'
testMessage1:
payload:
$ref: '#/components/schemas/objectWithKey'
testMessage2:
payload:
$ref: '#/components/schemas/objectWithKey2'
schemas:
objectWithKey:
type: object
properties:
key:
type: string
objectWithKey2:
type: object
properties:
key2:
type: string

View File

@@ -0,0 +1,117 @@
asyncapi: 3.0.0
info:
title: Notifications
version: 1.0.0
description: >-
This contract defines HTTP Push notification for application authorization
revocation topic
channels:
authRevoke:
address: AUTHORIZATION_REVOCATION
messages:
message:
$ref: '#/components/messages/message'
operations:
sendAuthRevoke:
action: send
channel:
$ref: '#/channels/authRevoke'
security:
- type: oauth2
description: The oauth security descriptions
flows:
clientCredentials:
tokenUrl: 'https://example.com/api/oauth/dialog'
availableScopes:
'subscribe:auth_revocations': Scope required for authorization revocation topic
scopes:
- 'subscribe:auth_revocations'
bindings:
http:
method: POST
messages:
- $ref: '#/channels/authRevoke/messages/message'
components:
messages:
message:
headers:
type: object
properties:
X-SIGNATURE:
description: ECC message signature
type: string
Content-Type:
type: string
enum:
- application/json
payload:
type: object
properties:
metadata:
$ref: '#/components/schemas/MetaData'
notification:
$ref: '#/components/schemas/Notification'
schemas:
MetaData:
type: object
properties:
topic:
type: string
description: Topic subscribed to.
schemaVersion:
type: string
description: The schema for this topic.
deprecated:
type: boolean
description: If this is a deprecated schema or topic.
default: 'false'
Notification:
type: object
properties:
notificationId:
type: string
description: The notification Id.
eventDate:
type: string
description: The event date associated with this notification in UTC.
publishDate:
type: string
description: The message publish date in UTC.
publishAttemptCount:
type: integer
description: The number of attempts made to publish this message.
data:
$ref: '#/components/schemas/AuthorizationRevocationData'
AuthorizationRevocationData:
type: object
description: The Authorization Revocation payload.
properties:
username:
type: string
description: The username for the user.
userId:
type: string
description: The immutable public userId for the user
eiasToken:
type: string
description: The legacy eiasToken specific to the user
revokeReason:
type: string
enum:
- REVOKED_BY_APP
- REVOKED_BY_USER
- REVOKED_BY_ADMIN
- PASSWORD_CHANGE
description: The reason for authorization revocation
revocationDate:
type: string
description: Date and time when the authorization was revoked
securitySchemes:
petstore_auth:
type: oauth2
description: The oauth security descriptions
flows:
clientCredentials:
tokenUrl: 'https://example.com/api/oauth/dialog'
availableScopes:
'subscribe:auth_revocations': Scope required for authorization revocation topic

View File

@@ -0,0 +1,36 @@
apis:
anyof:
root: ./anyof.yml
application-headers:
root: ./application-headers.yml
correlation-id:
root: ./correlation-id.yml
gitter-streaming:
root: ./gitter-streaming.yml
mercure:
root: ./mercure.yml
not:
root: ./not.yml
oneof:
root: ./oneof.yml
operation-security:
root: ./operation-security.yml
rpc-client:
root: ./rpc-client.yml
rpc-server:
root: ./rpc-server.yml
simple:
root: ./simple.yml
slack-rtm:
root: ./slack-rtm.yml
streetlights-kafka:
root: ./streetlights-kafka.yml
streetlights-mqtt:
root: ./streetlights-mqtt.yml
streetlights-operation-security:
root: ./streetlights-operation-security.yml
websocket-gemini:
root: ./websocket-gemini.yml
rules:
spec: error

View File

@@ -0,0 +1,72 @@
asyncapi: 3.0.0
id: 'urn:example:rpcclient'
info:
title: RPC Client Example
version: 1.0.0
description: This example demonstrates how to define an RPC client.
defaultContentType: application/json
servers:
production:
host: rabbitmq.example.org
protocol: amqp
channels:
queue:
address: '{queue}'
messages:
receiveSumResult:
correlationId:
location: $message.header#/correlation_id
payload:
type: object
properties:
result:
type: number
examples:
- 7
parameters:
queue: {}
bindings:
amqp:
is: queue
queue:
exclusive: true
rpc_queue:
address: rpc_queue
messages:
requestSum:
correlationId:
location: $message.header#/correlation_id
payload:
type: object
properties:
numbers:
type: array
items:
type: number
examples:
- - 4
- 3
bindings:
amqp:
is: queue
queue:
durable: false
operations:
receiveSumResult:
action: receive
channel:
$ref: '#/channels/queue'
bindings:
amqp:
ack: false
messages:
- $ref: '#/channels/queue/messages/receiveSumResult'
requestSum:
action: send
channel:
$ref: '#/channels/rpc_queue'
bindings:
amqp:
ack: true
messages:
- $ref: '#/channels/rpc_queue/messages/requestSum'

View File

@@ -0,0 +1,69 @@
asyncapi: 3.0.0
id: 'urn:example:rpcserver'
info:
title: RPC Server Example
version: 1.0.0
description: This example demonstrates how to define an RPC server.
defaultContentType: application/json
servers:
production:
host: rabbitmq.example.org
protocol: amqp
channels:
queue:
address: '{queue}'
messages:
sendSumResult:
correlationId:
location: $message.header#/correlation_id
payload:
type: object
properties:
result:
type: number
examples:
- 7
parameters:
queue: {}
bindings:
amqp:
is: queue
queue:
exclusive: true
rpc_queue:
address: rpc_queue
messages:
sum:
correlationId:
location: $message.header#/correlation_id
payload:
type: object
properties:
numbers:
type: array
items:
type: number
examples:
- - 4
- 3
bindings:
amqp:
is: queue
queue:
durable: false
operations:
sendSumResult:
action: send
channel:
$ref: '#/channels/queue'
bindings:
amqp:
ack: true
messages:
- $ref: '#/channels/queue/messages/sendSumResult'
sum:
action: receive
channel:
$ref: '#/channels/rpc_queue'
messages:
- $ref: '#/channels/rpc_queue/messages/sum'

View File

@@ -0,0 +1,31 @@
asyncapi: 3.0.0
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user signups
channels:
userSignedup:
address: user/signedup
messages:
UserSignedUp:
$ref: '#/components/messages/UserSignedUp'
operations:
sendUserSignedup:
action: send
channel:
$ref: '#/channels/userSignedup'
messages:
- $ref: '#/channels/userSignedup/messages/UserSignedUp'
components:
messages:
UserSignedUp:
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user

View File

@@ -0,0 +1,982 @@
asyncapi: 3.0.0
id: 'wss://wss-primary.slack.com/websocket'
info:
title: Slack Real Time Messaging API
version: 1.0.0
servers:
production:
host: slack.com
pathname: /api/rtm.connect
protocol: https
protocolVersion: '1.1'
security:
- $ref: '#/components/securitySchemes/token'
channels:
root:
address: /
messages:
outgoingMessage:
$ref: '#/components/messages/outgoingMessage'
hello:
$ref: '#/components/messages/hello'
connectionError:
$ref: '#/components/messages/connectionError'
accountsChanged:
$ref: '#/components/messages/accountsChanged'
botAdded:
$ref: '#/components/messages/botAdded'
botChanged:
$ref: '#/components/messages/botChanged'
channelArchive:
$ref: '#/components/messages/channelArchive'
channelCreated:
$ref: '#/components/messages/channelCreated'
channelDeleted:
$ref: '#/components/messages/channelDeleted'
channelHistoryChanged:
$ref: '#/components/messages/channelHistoryChanged'
channelJoined:
$ref: '#/components/messages/channelJoined'
channelLeft:
$ref: '#/components/messages/channelLeft'
channelMarked:
$ref: '#/components/messages/channelMarked'
channelRename:
$ref: '#/components/messages/channelRename'
channelUnarchive:
$ref: '#/components/messages/channelUnarchive'
commandsChanged:
$ref: '#/components/messages/commandsChanged'
dndUpdated:
$ref: '#/components/messages/dndUpdated'
dndUpdatedUser:
$ref: '#/components/messages/dndUpdatedUser'
emailDomainChanged:
$ref: '#/components/messages/emailDomainChanged'
emojiRemoved:
$ref: '#/components/messages/emojiRemoved'
emojiAdded:
$ref: '#/components/messages/emojiAdded'
fileChange:
$ref: '#/components/messages/fileChange'
fileCommentAdded:
$ref: '#/components/messages/fileCommentAdded'
fileCommentDeleted:
$ref: '#/components/messages/fileCommentDeleted'
fileCommentEdited:
$ref: '#/components/messages/fileCommentEdited'
fileCreated:
$ref: '#/components/messages/fileCreated'
fileDeleted:
$ref: '#/components/messages/fileDeleted'
filePublic:
$ref: '#/components/messages/filePublic'
fileShared:
$ref: '#/components/messages/fileShared'
fileUnshared:
$ref: '#/components/messages/fileUnshared'
goodbye:
$ref: '#/components/messages/goodbye'
groupArchive:
$ref: '#/components/messages/groupArchive'
groupClose:
$ref: '#/components/messages/groupClose'
groupHistoryChanged:
$ref: '#/components/messages/groupHistoryChanged'
groupJoined:
$ref: '#/components/messages/groupJoined'
groupLeft:
$ref: '#/components/messages/groupLeft'
groupMarked:
$ref: '#/components/messages/groupMarked'
groupOpen:
$ref: '#/components/messages/groupOpen'
groupRename:
$ref: '#/components/messages/groupRename'
groupUnarchive:
$ref: '#/components/messages/groupUnarchive'
imClose:
$ref: '#/components/messages/imClose'
imCreated:
$ref: '#/components/messages/imCreated'
imMarked:
$ref: '#/components/messages/imMarked'
imOpen:
$ref: '#/components/messages/imOpen'
manualPresenceChange:
$ref: '#/components/messages/manualPresenceChange'
memberJoinedChannel:
$ref: '#/components/messages/memberJoinedChannel'
message:
$ref: '#/components/messages/message'
operations:
receiveOutgoingMessage:
action: receive
channel:
$ref: '#/channels/root'
messages:
- $ref: '#/channels/root/messages/outgoingMessage'
sendMessages:
action: send
channel:
$ref: '#/channels/root'
messages:
- $ref: '#/channels/root/messages/hello'
- $ref: '#/channels/root/messages/connectionError'
- $ref: '#/channels/root/messages/accountsChanged'
- $ref: '#/channels/root/messages/botAdded'
- $ref: '#/channels/root/messages/botChanged'
- $ref: '#/channels/root/messages/channelArchive'
- $ref: '#/channels/root/messages/channelCreated'
- $ref: '#/channels/root/messages/channelDeleted'
- $ref: '#/channels/root/messages/channelHistoryChanged'
- $ref: '#/channels/root/messages/channelJoined'
- $ref: '#/channels/root/messages/channelLeft'
- $ref: '#/channels/root/messages/channelMarked'
- $ref: '#/channels/root/messages/channelRename'
- $ref: '#/channels/root/messages/channelUnarchive'
- $ref: '#/channels/root/messages/commandsChanged'
- $ref: '#/channels/root/messages/dndUpdated'
- $ref: '#/channels/root/messages/dndUpdatedUser'
- $ref: '#/channels/root/messages/emailDomainChanged'
- $ref: '#/channels/root/messages/emojiRemoved'
- $ref: '#/channels/root/messages/emojiAdded'
- $ref: '#/channels/root/messages/fileChange'
- $ref: '#/channels/root/messages/fileCommentAdded'
- $ref: '#/channels/root/messages/fileCommentDeleted'
- $ref: '#/channels/root/messages/fileCommentEdited'
- $ref: '#/channels/root/messages/fileCreated'
- $ref: '#/channels/root/messages/fileDeleted'
- $ref: '#/channels/root/messages/filePublic'
- $ref: '#/channels/root/messages/fileShared'
- $ref: '#/channels/root/messages/fileUnshared'
- $ref: '#/channels/root/messages/goodbye'
- $ref: '#/channels/root/messages/groupArchive'
- $ref: '#/channels/root/messages/groupClose'
- $ref: '#/channels/root/messages/groupHistoryChanged'
- $ref: '#/channels/root/messages/groupJoined'
- $ref: '#/channels/root/messages/groupLeft'
- $ref: '#/channels/root/messages/groupMarked'
- $ref: '#/channels/root/messages/groupOpen'
- $ref: '#/channels/root/messages/groupRename'
- $ref: '#/channels/root/messages/groupUnarchive'
- $ref: '#/channels/root/messages/imClose'
- $ref: '#/channels/root/messages/imCreated'
- $ref: '#/channels/root/messages/imMarked'
- $ref: '#/channels/root/messages/imOpen'
- $ref: '#/channels/root/messages/manualPresenceChange'
- $ref: '#/channels/root/messages/memberJoinedChannel'
- $ref: '#/channels/root/messages/message'
components:
securitySchemes:
token:
type: httpApiKey
name: token
in: query
schemas:
attachment:
type: object
properties:
fallback:
type: string
color:
type: string
pretext:
type: string
author_name:
type: string
author_link:
type: string
format: uri
author_icon:
type: string
format: uri
title:
type: string
title_link:
type: string
format: uri
text:
type: string
fields:
type: array
items:
type: object
properties:
title:
type: string
value:
type: string
short:
type: boolean
image_url:
type: string
format: uri
thumb_url:
type: string
format: uri
footer:
type: string
footer_icon:
type: string
format: uri
ts:
type: number
messages:
hello:
summary: First event received upon connection.
payload:
type: object
properties:
type:
type: string
enum:
- hello
connectionError:
summary: Event received when a connection error happens.
payload:
type: object
properties:
type:
type: string
enum:
- error
error:
type: object
properties:
code:
type: number
msg:
type: string
accountsChanged:
summary: The list of accounts a user is signed into has changed.
payload:
type: object
properties:
type:
type: string
enum:
- accounts_changed
botAdded:
summary: A bot user was added.
payload:
type: object
properties:
type:
type: string
enum:
- bot_added
bot:
type: object
properties:
id:
type: string
app_id:
type: string
name:
type: string
icons:
type: object
additionalProperties:
type: string
botChanged:
summary: A bot user was changed.
payload:
type: object
properties:
type:
type: string
enum:
- bot_added
bot:
type: object
properties:
id:
type: string
app_id:
type: string
name:
type: string
icons:
type: object
additionalProperties:
type: string
channelArchive:
summary: A channel was archived.
payload:
type: object
properties:
type:
type: string
enum:
- channel_archive
channel:
type: string
user:
type: string
channelCreated:
summary: A channel was created.
payload:
type: object
properties:
type:
type: string
enum:
- channel_created
channel:
type: object
properties:
id:
type: string
name:
type: string
created:
type: number
creator:
type: string
channelDeleted:
summary: A channel was deleted.
payload:
type: object
properties:
type:
type: string
enum:
- channel_deleted
channel:
type: string
channelHistoryChanged:
summary: Bulk updates were made to a channel's history.
payload:
type: object
properties:
type:
type: string
enum:
- channel_history_changed
latest:
type: string
ts:
type: string
event_ts:
type: string
channelJoined:
summary: You joined a channel.
payload:
type: object
properties:
type:
type: string
enum:
- channel_joined
channel:
type: object
properties:
id:
type: string
name:
type: string
created:
type: number
creator:
type: string
channelLeft:
summary: You left a channel.
payload:
type: object
properties:
type:
type: string
enum:
- channel_left
channel:
type: string
channelMarked:
summary: Your channel read marker was updated.
payload:
type: object
properties:
type:
type: string
enum:
- channel_marked
channel:
type: string
ts:
type: string
channelRename:
summary: A channel was renamed.
payload:
type: object
properties:
type:
type: string
enum:
- channel_rename
channel:
type: object
properties:
id:
type: string
name:
type: string
created:
type: number
channelUnarchive:
summary: A channel was unarchived.
payload:
type: object
properties:
type:
type: string
enum:
- channel_unarchive
channel:
type: string
user:
type: string
commandsChanged:
summary: A slash command has been added or changed.
payload:
type: object
properties:
type:
type: string
enum:
- commands_changed
event_ts:
type: string
dndUpdated:
summary: Do not Disturb settings changed for the current user.
payload:
type: object
properties:
type:
type: string
enum:
- dnd_updated
user:
type: string
dnd_status:
type: object
properties:
dnd_enabled:
type: boolean
next_dnd_start_ts:
type: number
next_dnd_end_ts:
type: number
snooze_enabled:
type: boolean
snooze_endtime:
type: number
dndUpdatedUser:
summary: Do not Disturb settings changed for a member.
payload:
type: object
properties:
type:
type: string
enum:
- dnd_updated_user
user:
type: string
dnd_status:
type: object
properties:
dnd_enabled:
type: boolean
next_dnd_start_ts:
type: number
next_dnd_end_ts:
type: number
emailDomainChanged:
summary: The workspace email domain has changed.
payload:
type: object
properties:
type:
type: string
enum:
- email_domain_changed
email_domain:
type: string
event_ts:
type: string
emojiRemoved:
summary: A custom emoji has been removed.
payload:
type: object
properties:
type:
type: string
enum:
- emoji_changed
subtype:
type: string
enum:
- remove
names:
type: array
items:
type: string
event_ts:
type: string
emojiAdded:
summary: A custom emoji has been added.
payload:
type: object
properties:
type:
type: string
enum:
- emoji_changed
subtype:
type: string
enum:
- add
name:
type: string
value:
type: string
format: uri
event_ts:
type: string
fileChange:
summary: A file was changed.
payload:
type: object
properties:
type:
type: string
enum:
- file_change
file_id:
type: string
file:
type: object
properties:
id:
type: string
fileCommentAdded:
summary: A file comment was added.
payload:
type: object
properties:
type:
type: string
enum:
- file_comment_added
comment: {}
file_id:
type: string
file:
type: object
properties:
id:
type: string
fileCommentDeleted:
summary: A file comment was deleted.
payload:
type: object
properties:
type:
type: string
enum:
- file_comment_deleted
comment:
type: string
file_id:
type: string
file:
type: object
properties:
id:
type: string
fileCommentEdited:
summary: A file comment was edited.
payload:
type: object
properties:
type:
type: string
enum:
- file_comment_edited
comment: {}
file_id:
type: string
file:
type: object
properties:
id:
type: string
fileCreated:
summary: A file was created.
payload:
type: object
properties:
type:
type: string
enum:
- file_created
file_id:
type: string
file:
type: object
properties:
id:
type: string
fileDeleted:
summary: A file was deleted.
payload:
type: object
properties:
type:
type: string
enum:
- file_deleted
file_id:
type: string
event_ts:
type: string
filePublic:
summary: A file was made public.
payload:
type: object
properties:
type:
type: string
enum:
- file_public
file_id:
type: string
file:
type: object
properties:
id:
type: string
fileShared:
summary: A file was shared.
payload:
type: object
properties:
type:
type: string
enum:
- file_shared
file_id:
type: string
file:
type: object
properties:
id:
type: string
fileUnshared:
summary: A file was unshared.
payload:
type: object
properties:
type:
type: string
enum:
- file_unshared
file_id:
type: string
file:
type: object
properties:
id:
type: string
goodbye:
summary: The server intends to close the connection soon.
payload:
type: object
properties:
type:
type: string
enum:
- goodbye
groupArchive:
summary: A private channel was archived.
payload:
type: object
properties:
type:
type: string
enum:
- group_archive
channel:
type: string
groupClose:
summary: You closed a private channel.
payload:
type: object
properties:
type:
type: string
enum:
- group_close
user:
type: string
channel:
type: string
groupHistoryChanged:
summary: Bulk updates were made to a private channel's history.
payload:
type: object
properties:
type:
type: string
enum:
- group_history_changed
latest:
type: string
ts:
type: string
event_ts:
type: string
groupJoined:
summary: You joined a private channel.
payload:
type: object
properties:
type:
type: string
enum:
- group_joined
channel:
type: object
properties:
id:
type: string
name:
type: string
created:
type: number
creator:
type: string
groupLeft:
summary: You left a private channel.
payload:
type: object
properties:
type:
type: string
enum:
- group_left
channel:
type: string
groupMarked:
summary: A private channel read marker was updated.
payload:
type: object
properties:
type:
type: string
enum:
- group_marked
channel:
type: string
ts:
type: string
groupOpen:
summary: You opened a private channel.
payload:
type: object
properties:
type:
type: string
enum:
- group_open
user:
type: string
channel:
type: string
groupRename:
summary: A private channel was renamed.
payload:
type: object
properties:
type:
type: string
enum:
- group_rename
channel:
type: object
properties:
id:
type: string
name:
type: string
created:
type: number
groupUnarchive:
summary: A private channel was unarchived.
payload:
type: object
properties:
type:
type: string
enum:
- group_unarchive
channel:
type: string
user:
type: string
imClose:
summary: You closed a DM.
payload:
type: object
properties:
type:
type: string
enum:
- im_close
channel:
type: string
user:
type: string
imCreated:
summary: A DM was created.
payload:
type: object
properties:
type:
type: string
enum:
- im_created
channel:
type: object
properties:
id:
type: string
name:
type: string
created:
type: number
creator:
type: string
user:
type: string
imMarked:
summary: A direct message read marker was updated.
payload:
type: object
properties:
type:
type: string
enum:
- im_marked
channel:
type: string
ts:
type: string
imOpen:
summary: You opened a DM.
payload:
type: object
properties:
type:
type: string
enum:
- im_open
channel:
type: string
user:
type: string
manualPresenceChange:
summary: You manually updated your presence.
payload:
type: object
properties:
type:
type: string
enum:
- manual_presence_change
presence:
type: string
memberJoinedChannel:
summary: A user joined a public or private channel.
payload:
type: object
properties:
type:
type: string
enum:
- member_joined_channel
user:
type: string
channel:
type: string
channel_type:
type: string
enum:
- C
- G
team:
type: string
inviter:
type: string
memberLeftChannel:
summary: A user left a public or private channel.
payload:
type: object
properties:
type:
type: string
enum:
- member_left_channel
user:
type: string
channel:
type: string
channel_type:
type: string
enum:
- C
- G
team:
type: string
message:
summary: A message was sent to a channel.
payload:
type: object
properties:
type:
type: string
enum:
- message
user:
type: string
channel:
type: string
text:
type: string
ts:
type: string
attachments:
type: array
items:
$ref: '#/components/schemas/attachment'
edited:
type: object
properties:
user:
type: string
ts:
type: string
outgoingMessage:
summary: A message was sent to a channel.
payload:
type: object
properties:
id:
type: number
type:
type: string
enum:
- message
channel:
type: string
text:
type: string

View File

@@ -0,0 +1,56 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`E2E lint async3 1`] = `
validating /anyof.yml...
/anyof.yml: validated in <test>ms
validating /application-headers.yml...
/application-headers.yml: validated in <test>ms
validating /correlation-id.yml...
/correlation-id.yml: validated in <test>ms
validating /gitter-streaming.yml...
/gitter-streaming.yml: validated in <test>ms
validating /mercure.yml...
/mercure.yml: validated in <test>ms
validating /not.yml...
/not.yml: validated in <test>ms
validating /oneof.yml...
/oneof.yml: validated in <test>ms
validating /operation-security.yml...
/operation-security.yml: validated in <test>ms
validating /rpc-client.yml...
/rpc-client.yml: validated in <test>ms
validating /rpc-server.yml...
/rpc-server.yml: validated in <test>ms
validating /simple.yml...
/simple.yml: validated in <test>ms
validating /slack-rtm.yml...
/slack-rtm.yml: validated in <test>ms
validating /streetlights-kafka.yml...
/streetlights-kafka.yml: validated in <test>ms
validating /streetlights-mqtt.yml...
/streetlights-mqtt.yml: validated in <test>ms
validating /streetlights-operation-security.yml...
/streetlights-operation-security.yml: validated in <test>ms
validating /websocket-gemini.yml...
/websocket-gemini.yml: validated in <test>ms
Woohoo! Your API descriptions are valid. 🎉
`;

View File

@@ -0,0 +1,199 @@
asyncapi: 3.0.0
info:
title: Streetlights Kafka API
version: 1.0.0
description: "The Smartylighting Streetlights API allows you to remotely manage the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time information about environmental lighting conditions \U0001F4C8\n"
license:
name: Apache 2.0
url: 'https://www.apache.org/licenses/LICENSE-2.0'
defaultContentType: application/json
servers:
scram-connections:
host: 'test.mykafkacluster.org:18092'
protocol: kafka-secure
description: Test broker secured with scramSha256
security:
- $ref: '#/components/securitySchemes/saslScram'
tags:
- name: 'env:test-scram'
description: >-
This environment is meant for running internal tests through
scramSha256
- name: 'kind:remote'
description: This server is a remote server. Not exposed by the application
- name: 'visibility:private'
description: This resource is private and only available to certain users
mtls-connections:
host: 'test.mykafkacluster.org:28092'
protocol: kafka-secure
description: Test broker secured with X509
security:
- $ref: '#/components/securitySchemes/certs'
tags:
- name: 'env:test-mtls'
description: This environment is meant for running internal tests through mtls
- name: 'kind:remote'
description: This server is a remote server. Not exposed by the application
- name: 'visibility:private'
description: This resource is private and only available to certain users
channels:
lightingMeasured:
address: 'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured'
messages:
lightMeasured:
$ref: '#/components/messages/lightMeasured'
description: The topic on which measured values may be produced and consumed.
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
lightTurnOn:
address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.on'
messages:
turnOn:
$ref: '#/components/messages/turnOnOff'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
lightTurnOff:
address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.off'
messages:
turnOff:
$ref: '#/components/messages/turnOnOff'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
lightsDim:
address: 'smartylighting.streetlights.1.0.action.{streetlightId}.dim'
messages:
dimLight:
$ref: '#/components/messages/dimLight'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
operations:
receiveLightMeasurement:
action: receive
channel:
$ref: '#/channels/lightingMeasured'
summary: >-
Inform about environmental lighting conditions of a particular
streetlight.
traits:
- $ref: '#/components/operationTraits/kafka'
messages:
- $ref: '#/channels/lightingMeasured/messages/lightMeasured'
turnOn:
action: send
channel:
$ref: '#/channels/lightTurnOn'
traits:
- $ref: '#/components/operationTraits/kafka'
messages:
- $ref: '#/channels/lightTurnOn/messages/turnOn'
turnOff:
action: send
channel:
$ref: '#/channels/lightTurnOff'
traits:
- $ref: '#/components/operationTraits/kafka'
messages:
- $ref: '#/channels/lightTurnOff/messages/turnOff'
dimLight:
action: send
channel:
$ref: '#/channels/lightsDim'
traits:
- $ref: '#/components/operationTraits/kafka'
messages:
- $ref: '#/channels/lightsDim/messages/dimLight'
components:
messages:
lightMeasured:
name: lightMeasured
title: Light measured
summary: >-
Inform about environmental lighting conditions of a particular
streetlight.
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/lightMeasuredPayload'
turnOnOff:
name: turnOnOff
title: Turn on/off
summary: Command a particular streetlight to turn the lights on or off.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/turnOnOffPayload'
dimLight:
name: dimLight
title: Dim light
summary: Command a particular streetlight to dim the lights.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/dimLightPayload'
schemas:
lightMeasuredPayload:
type: object
properties:
lumens:
type: integer
minimum: 0
description: Light intensity measured in lumens.
sentAt:
$ref: '#/components/schemas/sentAt'
turnOnOffPayload:
type: object
properties:
command:
type: string
enum:
- 'on'
- 'off'
description: Whether to turn on or off the light.
sentAt:
$ref: '#/components/schemas/sentAt'
dimLightPayload:
type: object
properties:
percentage:
type: integer
description: Percentage to which the light should be dimmed to.
minimum: 0
maximum: 100
sentAt:
$ref: '#/components/schemas/sentAt'
sentAt:
type: string
format: date-time
description: Date and time when the message was sent.
securitySchemes:
saslScram:
type: scramSha256
description: Provide your username and password for SASL/SCRAM authentication
certs:
type: X509
description: Download the certificate files from service provider
parameters:
streetlightId:
description: The ID of the streetlight.
messageTraits:
commonHeaders:
headers:
type: object
properties:
my-app-header:
type: integer
minimum: 0
maximum: 100
operationTraits:
kafka:
bindings:
kafka:
clientId:
type: string
enum:
- my-app-id

View File

@@ -0,0 +1,253 @@
asyncapi: 3.0.0
info:
title: Streetlights MQTT API
version: 1.0.0
description: "The Smartylighting Streetlights API allows you to remotely manage the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time information about environmental lighting conditions \U0001F4C8\n"
license:
name: Apache 2.0
url: 'https://www.apache.org/licenses/LICENSE-2.0'
defaultContentType: application/json
servers:
production:
host: 'test.mosquitto.org:{port}'
protocol: mqtt
description: Test broker
variables:
port:
description: Secure connection (TLS) is available through port 8883.
default: '1883'
enum:
- '1883'
- '8883'
security:
- $ref: '#/components/securitySchemes/apiKey'
- type: oauth2
description: Flows to support OAuth 2.0
flows:
implicit:
authorizationUrl: 'https://authserver.example/auth'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
password:
tokenUrl: 'https://authserver.example/token'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
clientCredentials:
tokenUrl: 'https://authserver.example/token'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
authorizationCode:
authorizationUrl: 'https://authserver.example/auth'
tokenUrl: 'https://authserver.example/token'
refreshUrl: 'https://authserver.example/refresh'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
scopes:
- 'streetlights:on'
- 'streetlights:off'
- 'streetlights:dim'
- $ref: '#/components/securitySchemes/openIdConnectWellKnown'
tags:
- name: 'env:production'
description: This environment is meant for production use case
- name: 'kind:remote'
description: This server is a remote server. Not exposed by the application
- name: 'visibility:public'
description: This resource is public and available to everyone
channels:
lightingMeasured:
address: 'smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured'
messages:
lightMeasured:
$ref: '#/components/messages/lightMeasured'
description: The topic on which measured values may be produced and consumed.
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
lightTurnOn:
address: 'smartylighting/streetlights/1/0/action/{streetlightId}/turn/on'
messages:
turnOn:
$ref: '#/components/messages/turnOnOff'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
lightTurnOff:
address: 'smartylighting/streetlights/1/0/action/{streetlightId}/turn/off'
messages:
turnOff:
$ref: '#/components/messages/turnOnOff'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
lightsDim:
address: 'smartylighting/streetlights/1/0/action/{streetlightId}/dim'
messages:
dimLight:
$ref: '#/components/messages/dimLight'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
operations:
receiveLightMeasurement:
action: receive
channel:
$ref: '#/channels/lightingMeasured'
summary: >-
Inform about environmental lighting conditions of a particular
streetlight.
traits:
- $ref: '#/components/operationTraits/mqtt'
messages:
- $ref: '#/channels/lightingMeasured/messages/lightMeasured'
turnOn:
action: send
channel:
$ref: '#/channels/lightTurnOn'
traits:
- $ref: '#/components/operationTraits/mqtt'
messages:
- $ref: '#/channels/lightTurnOn/messages/turnOn'
turnOff:
action: send
channel:
$ref: '#/channels/lightTurnOff'
traits:
- $ref: '#/components/operationTraits/mqtt'
messages:
- $ref: '#/channels/lightTurnOff/messages/turnOff'
dimLight:
action: send
channel:
$ref: '#/channels/lightsDim'
traits:
- $ref: '#/components/operationTraits/mqtt'
messages:
- $ref: '#/channels/lightsDim/messages/dimLight'
components:
messages:
lightMeasured:
name: lightMeasured
title: Light measured
summary: >-
Inform about environmental lighting conditions of a particular
streetlight.
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/lightMeasuredPayload'
turnOnOff:
name: turnOnOff
title: Turn on/off
summary: Command a particular streetlight to turn the lights on or off.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/turnOnOffPayload'
dimLight:
name: dimLight
title: Dim light
summary: Command a particular streetlight to dim the lights.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/dimLightPayload'
schemas:
lightMeasuredPayload:
type: object
properties:
lumens:
type: integer
minimum: 0
description: Light intensity measured in lumens.
sentAt:
$ref: '#/components/schemas/sentAt'
turnOnOffPayload:
type: object
properties:
command:
type: string
enum:
- 'on'
- 'off'
description: Whether to turn on or off the light.
sentAt:
$ref: '#/components/schemas/sentAt'
dimLightPayload:
type: object
properties:
percentage:
type: integer
description: Percentage to which the light should be dimmed to.
minimum: 0
maximum: 100
sentAt:
$ref: '#/components/schemas/sentAt'
sentAt:
type: string
format: date-time
description: Date and time when the message was sent.
securitySchemes:
apiKey:
type: apiKey
in: user
description: Provide your API key as the user and leave the password empty.
supportedOauthFlows:
type: oauth2
description: Flows to support OAuth 2.0
flows:
implicit:
authorizationUrl: 'https://authserver.example/auth'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
password:
tokenUrl: 'https://authserver.example/token'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
clientCredentials:
tokenUrl: 'https://authserver.example/token'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
authorizationCode:
authorizationUrl: 'https://authserver.example/auth'
tokenUrl: 'https://authserver.example/token'
refreshUrl: 'https://authserver.example/refresh'
availableScopes:
'streetlights:on': Ability to switch lights on
'streetlights:off': Ability to switch lights off
'streetlights:dim': Ability to dim the lights
openIdConnectWellKnown:
type: openIdConnect
openIdConnectUrl: 'https://authserver.example/.well-known'
parameters:
streetlightId:
description: The ID of the streetlight.
messageTraits:
commonHeaders:
headers:
type: object
properties:
my-app-header:
type: integer
minimum: 0
maximum: 100
operationTraits:
mqtt:
bindings:
mqtt:
qos: 1

View File

@@ -0,0 +1,240 @@
asyncapi: 3.0.0
info:
title: Streetlights Kafka API
version: 1.0.0
description: "The Smartylighting Streetlights API allows you to remotely manage the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time information about environmental lighting conditions \U0001F4C8\n"
license:
name: Apache 2.0
url: 'https://www.apache.org/licenses/LICENSE-2.0'
defaultContentType: application/json
servers:
test:
host: 'test.mykafkacluster.org:8092'
protocol: kafka-secure
description: Test broker
security:
- $ref: '#/components/securitySchemes/saslScram'
test_oauth:
host: 'test.mykafkacluster.org:8093'
protocol: kafka-secure
description: Test port for oauth
security:
- type: oauth2
description: The oauth security descriptions
flows:
clientCredentials:
tokenUrl: 'https://example.com/api/oauth/dialog'
availableScopes:
'streetlights:read': Scope required for subscribing to channel
'streetlights:write': Scope required for publishing to channel
scopes:
- 'streetlights:write'
- 'streetlights:read'
channels:
lightingMeasured:
address: 'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured'
messages:
lightMeasured:
$ref: '#/components/messages/lightMeasured'
description: The topic on which measured values may be produced and consumed.
servers:
- $ref: '#/servers/test'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
lightTurnOn:
address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.on'
messages:
turnOn:
$ref: '#/components/messages/turnOnOff'
servers:
- $ref: '#/servers/test_oauth'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
lightTurnOff:
address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.off'
messages:
turnOff:
$ref: '#/components/messages/turnOnOff'
servers:
- $ref: '#/servers/test_oauth'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
lightsDim:
address: 'smartylighting.streetlights.1.0.action.{streetlightId}.dim'
messages:
dimLight:
$ref: '#/components/messages/dimLight'
servers:
- $ref: '#/servers/test_oauth'
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'
operations:
receiveLightMeasurement:
action: receive
channel:
$ref: '#/channels/lightingMeasured'
summary: >-
Inform about environmental lighting conditions of a particular
streetlight.
traits:
- $ref: '#/components/operationTraits/kafka'
messages:
- $ref: '#/channels/lightingMeasured/messages/lightMeasured'
turnOn:
action: send
channel:
$ref: '#/channels/lightTurnOn'
security:
- type: oauth2
description: The oauth security descriptions
flows:
clientCredentials:
tokenUrl: 'https://example.com/api/oauth/dialog'
availableScopes:
'streetlights:read': Scope required for subscribing to channel
'streetlights:write': Scope required for publishing to channel
scopes:
- 'streetlights:read'
traits:
- $ref: '#/components/operationTraits/kafka'
messages:
- $ref: '#/channels/lightTurnOn/messages/turnOn'
turnOff:
action: send
channel:
$ref: '#/channels/lightTurnOff'
security:
- type: oauth2
description: The oauth security descriptions
flows:
clientCredentials:
tokenUrl: 'https://example.com/api/oauth/dialog'
availableScopes:
'streetlights:read': Scope required for subscribing to channel
'streetlights:write': Scope required for publishing to channel
scopes:
- 'streetlights:read'
traits:
- $ref: '#/components/operationTraits/kafka'
messages:
- $ref: '#/channels/lightTurnOff/messages/turnOff'
dimLight:
action: send
channel:
$ref: '#/channels/lightsDim'
security:
- type: oauth2
description: The oauth security descriptions
flows:
clientCredentials:
tokenUrl: 'https://example.com/api/oauth/dialog'
availableScopes:
'streetlights:read': Scope required for subscribing to channel
'streetlights:write': Scope required for publishing to channel
scopes:
- 'streetlights:read'
traits:
- $ref: '#/components/operationTraits/kafka'
messages:
- $ref: '#/channels/lightsDim/messages/dimLight'
components:
messages:
lightMeasured:
name: lightMeasured
title: Light measured
summary: >-
Inform about environmental lighting conditions of a particular
streetlight.
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/lightMeasuredPayload'
turnOnOff:
name: turnOnOff
title: Turn on/off
summary: Command a particular streetlight to turn the lights on or off.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/turnOnOffPayload'
dimLight:
name: dimLight
title: Dim light
summary: Command a particular streetlight to dim the lights.
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/dimLightPayload'
schemas:
lightMeasuredPayload:
type: object
properties:
lumens:
type: integer
minimum: 0
description: Light intensity measured in lumens.
sentAt:
$ref: '#/components/schemas/sentAt'
turnOnOffPayload:
type: object
properties:
command:
type: string
enum:
- 'on'
- 'off'
description: Whether to turn on or off the light.
sentAt:
$ref: '#/components/schemas/sentAt'
dimLightPayload:
type: object
properties:
percentage:
type: integer
description: Percentage to which the light should be dimmed to.
minimum: 0
maximum: 100
sentAt:
$ref: '#/components/schemas/sentAt'
sentAt:
type: string
format: date-time
description: Date and time when the message was sent.
securitySchemes:
saslScram:
type: scramSha256
description: Provide your username and password for SASL/SCRAM authentication
streetlights_auth:
type: oauth2
description: The oauth security descriptions
flows:
clientCredentials:
tokenUrl: 'https://example.com/api/oauth/dialog'
availableScopes:
'streetlights:read': Scope required for subscribing to channel
'streetlights:write': Scope required for publishing to channel
parameters:
streetlightId:
description: The ID of the streetlight.
messageTraits:
commonHeaders:
headers:
type: object
properties:
my-app-header:
type: integer
minimum: 0
maximum: 100
operationTraits:
kafka:
bindings:
kafka:
clientId:
type: string
enum:
- my-app-id

View File

@@ -0,0 +1,301 @@
asyncapi: 3.0.0
info:
title: Gemini Market Data Websocket API
version: 1.0.0
description: >
Market data is a public API that streams all the market data on a given
symbol.
You can quickly play with the API using
[websocat](https://github.com/vi/websocat#installation) like this:
```bash
websocat wss://api.gemini.com/v1/marketdata/btcusd?heartbeat=true -S
```
contact:
name: Gemini
url: 'https://www.gemini.com/'
externalDocs:
url: 'https://docs.sandbox.gemini.com/websocket-api/#market-data'
servers:
public:
host: api.gemini.com
protocol: wss
channels:
marketDataV1:
address: '/v1/marketdata/{symbol}'
messages:
marketData:
$ref: '#/components/messages/marketData'
parameters:
symbol:
enum:
- btcusd
- ethbtc
- ethusd
- zecusd
- zecbtc
- zeceth
- zecbch
- zecltc
- bchusd
- bchbtc
- bcheth
- ltcusd
- ltcbtc
- ltceth
- ltcbch
- batusd
- daiusd
- linkusd
- oxtusd
- batbtc
- linkbtc
- oxtbtc
- bateth
- linketh
- oxteth
- ampusd
- compusd
- paxgusd
- mkrusd
- zrxusd
- kncusd
- manausd
- storjusd
- snxusd
- crvusd
- balusd
- uniusd
- renusd
- umausd
- yfiusd
- btcdai
- ethdai
- aaveusd
- filusd
- btceur
- btcgbp
- etheur
- ethgbp
- btcsgd
- ethsgd
- sklusd
- grtusd
- bntusd
- 1inchusd
- enjusd
- lrcusd
- sandusd
- cubeusd
- lptusd
- bondusd
- maticusd
- injusd
- sushiusd
description: >
Symbols are formatted as CCY1CCY2 where prices are in CCY2 and
quantities are in CCY1. To read more click
[here](https://docs.sandbox.gemini.com/websocket-api/#symbols-and-minimums).
bindings:
ws:
bindingVersion: 0.1.0
query:
type: object
description: >
The semantics of entry type filtering is:
If any entry type is specified as true or false, all of them must be
explicitly flagged true to show up in the response
If no entry types filtering parameters are included in the url, then
all entry types will appear in the response
NOTE: top_of_book has no meaning and initial book events are empty
when only trades is specified
properties:
heartbeat:
type: boolean
default: false
description: >-
Optionally add this parameter and set to true to receive a
heartbeat every 5 seconds
top_of_book:
type: boolean
default: false
description: >-
If absent or false, receive full order book depth; if present
and true, receive top of book only. Only applies to bids and
offers.
bids:
type: boolean
default: true
description: Include bids in change events
offers:
type: boolean
default: true
description: Include asks in change events
trades:
type: boolean
default: true
description: Include trade events
auctions:
type: boolean
default: true
description: Include auction events
operations:
sendMarketData:
action: send
channel:
$ref: '#/channels/marketDataV1'
summary: Receive market updates on a given symbol
messages:
- $ref: '#/channels/marketDataV1/messages/marketData'
components:
messages:
marketData:
summary: Message with marked data information.
description: >
The initial response message will show the existing state of the order
book. Subsequent messages will show all executed trades, as well as all
other changes to the order book from orders placed or canceled.
payload:
$ref: '#/components/schemas/market'
examples:
- name: updateMessage
summary: >-
Example of an update message that contains a change in price
information.
payload:
type: update
eventId: 36902233362
timestamp: 1619769673
timestampms: 1619769673527
socket_sequence: 661
events:
- type: change
side: bid
price: 54350.40
remaining: 0.002
delta: 0.002
reason: place
- name: heartbeatMessage
summary: Example of additional heartbeat message when you enable them.
payload:
type: heartbeat
socket_sequence: 1656
schemas:
market:
type: object
oneOf:
- $ref: '#/components/schemas/heartbeat'
- $ref: '#/components/schemas/update'
heartbeat:
allOf:
- properties:
type:
type: string
const: heartbeat
required:
- type
- $ref: '#/components/schemas/default'
update:
allOf:
- properties:
type:
type: string
const: update
eventId:
type: integer
description: >-
A monotonically increasing sequence number indicating when this
change occurred. These numbers are persistent and consistent
between market data connections.
events:
$ref: '#/components/schemas/events'
timestamp:
type: number
description: >-
The timestamp in seconds for this group of events (included for
compatibility reasons). We recommend using the timestampms field
instead.
timestampms:
type: number
description: The timestamp in milliseconds for this group of events.
required:
- type
- eventId
- events
- timestamp
- timestampms
- $ref: '#/components/schemas/default'
default:
type: object
description: >-
This object is always part of the payload. In case of type=heartbeat,
these are the only fields.
required:
- type
- socket_sequence
properties:
socket_sequence:
type: integer
description: >-
zero-indexed monotonic increasing sequence number attached to each
message sent - if there is a gap in this sequence, you have missed a
message. If you choose to enable heartbeats, then heartbeat and
update messages will share a single increasing sequence. See
[Sequence
Numbers](https://docs.sandbox.gemini.com/websocket-api/#sequence-numbers)
for more information.
events:
type: array
description: >-
Either a change to the order book, or the indication that a trade has
occurred.
items:
type: object
additionalProperties: false
properties:
type:
type: string
enum:
- trade
- change
- 'auction, block_trade'
price:
type: number
multipleOf: 0.01
description: The price of this order book entry.
side:
type: string
enum:
- bid
- side
reason:
type: string
enum:
- place
- trade
- cancel
- initial
description: >-
Indicates why the change has occurred. initial is for the initial
response message, which will show the entire existing state of the
order book.
remaining:
type: number
description: >-
The quantity remaining at that price level after this change
occurred. May be zero if all orders at this price level have been
filled or canceled.
delta:
type: number
description: >-
The quantity changed. May be negative, if an order is filled or
canceled. For initial messages, delta will equal remaining.

View File

@@ -15,6 +15,7 @@ Redocly CLI supports the following linting approaches with AsyncAPI documents:
- AsyncAPI document validation, including full binding validation for [supported protocols](#supported-protocols).
- Supported versions:
- [AsyncAPI 3.0](https://www.asyncapi.com/docs/reference/specification/v3.0.0)
- [AsyncAPI 2.6](https://v2.asyncapi.com/docs/reference/specification/v2.6.0)
- earlier versions in the 2.x family may also validate successfully
- Built-in rules for checking common standards requirements (see the [list of AsyncAPI rules](#asyncapi-rules)).

View File

@@ -73,6 +73,7 @@ export enum SpecVersion {
OAS3_0 = 'oas3_0',
OAS3_1 = 'oas3_1',
Async2 = 'async2',
Async3 = 'async3',
}
export enum Oas3Operations {

View File

@@ -474,6 +474,7 @@ describe('checkIfRulesetExist', () => {
oas3_0: {},
oas3_1: {},
async2: {},
async3: {},
arazzo: {},
};
expect(() => checkIfRulesetExist(rules)).toThrowError(

View File

@@ -522,6 +522,7 @@ export function checkIfRulesetExist(rules: typeof StyleguideConfig.prototype.rul
...rules.oas3_0,
...rules.oas3_1,
...rules.async2,
...rules.async3,
...rules.arazzo,
};

View File

@@ -274,3 +274,145 @@ describe('bundleFromString', () => {
expect(rest.source.body).toEqual(stringDocument);
});
});
describe('bundle async', () => {
it('should bundle async of version 2.x', async () => {
const testDocument = parseYamlToDocument(
outdent`
asyncapi: '2.6.0'
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user signups
channels:
user/signedup:
subscribe:
message:
$ref: '#/components/messages/UserSignedUp'
components:
schemas:
UserSignedUp:
type: object
properties:
displayName:
type: string
description: Name of the user
messages:
UserSignedUp:
payload:
$ref: '#/components/schemas/UserSignedUp'
`,
''
);
const config = await makeConfig({});
const {
bundle: { parsed },
problems,
} = await bundleDocument({
document: testDocument,
config: config,
externalRefResolver: new BaseResolver(),
dereference: true,
});
expect(problems).toHaveLength(0);
expect(parsed).toMatchInlineSnapshot(`
asyncapi: 2.6.0
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user signups
channels:
user/signedup:
subscribe:
message:
payload: &ref_1
type: object
properties: &ref_0
displayName:
type: string
description: Name of the user
components:
schemas:
UserSignedUp:
type: object
properties: *ref_0
messages:
UserSignedUp:
payload: *ref_1
`);
});
it('should bundle async of version 3.0', async () => {
const testDocument = parseYamlToDocument(
outdent`
asyncapi: 3.0.0
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user signups
operations:
sendUserSignedup:
action: send
messages:
- $ref: '#/components/messages/UserSignedUp'
components:
schemas:
UserSignedUp:
type: object
properties:
displayName:
type: string
description: Name of the user
messages:
UserSignedUp:
payload:
$ref: '#/components/schemas/UserSignedUp'
`,
''
);
const config = await makeConfig({});
const {
bundle: { parsed },
problems,
} = await bundleDocument({
document: testDocument,
config: config,
externalRefResolver: new BaseResolver(),
dereference: true,
});
expect(problems).toHaveLength(0);
expect(parsed).toMatchInlineSnapshot(`
asyncapi: 3.0.0
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user signups
operations:
sendUserSignedup:
action: send
messages:
- payload: &ref_1
type: object
properties: &ref_0
displayName:
type: string
description: Name of the user
components:
schemas:
UserSignedUp:
type: object
properties: *ref_0
messages:
UserSignedUp:
payload: *ref_1
`);
});
});

View File

@@ -292,6 +292,15 @@ export function mapTypeToComponent(typeName: string, version: SpecMajorVersion)
default:
return null;
}
case SpecMajorVersion.Async3:
switch (typeName) {
case 'Schema':
return 'schemas';
case 'Parameter':
return 'parameters';
default:
return null;
}
case SpecMajorVersion.Arazzo:
switch (typeName) {
case 'Root.x-parameters_items':
@@ -377,6 +386,8 @@ function makeBundleVisitor(
components = root;
} else if (version === SpecMajorVersion.Async2) {
components = root.components = root.components || {};
} else if (version === SpecMajorVersion.Async3) {
components = root.components = root.components || {};
} else if (version === SpecMajorVersion.Arazzo) {
components = root.components = root.components || {};
}
@@ -427,6 +438,8 @@ function makeBundleVisitor(
components[componentType][name] = target.node;
if (version === SpecMajorVersion.OAS3) {
return `#/components/${componentType}/${name}`;
} else if (version === SpecMajorVersion.Async2 || version === SpecMajorVersion.Async3) {
return `#/components/${componentType}/${name}`;
} else {
return `#/${componentType}/${name}`;
}

View File

@@ -18,6 +18,17 @@ exports[`resolveConfig should ignore minimal from the root and read local file 1
"tag-description": "warn",
"tags-alphabetical": "off",
},
"async3Decorators": {},
"async3Preprocessors": {},
"async3Rules": {
"channels-kebab-case": "off",
"info-contact": "off",
"no-channel-trailing-slash": "off",
"operation-operationId": "warn",
"spec": "error",
"tag-description": "warn",
"tags-alphabetical": "off",
},
"decorators": {},
"doNotResolveExamples": undefined,
"oas2Decorators": {},
@@ -135,6 +146,17 @@ exports[`resolveStyleguideConfig should resolve extends with local file config w
"tag-description": "warn",
"tags-alphabetical": "off",
},
"async3Decorators": {},
"async3Preprocessors": {},
"async3Rules": {
"channels-kebab-case": "off",
"info-contact": "off",
"no-channel-trailing-slash": "off",
"operation-operationId": "warn",
"spec": "error",
"tag-description": "warn",
"tags-alphabetical": "off",
},
"decorators": {},
"doNotResolveExamples": undefined,
"oas2Decorators": {},

View File

@@ -12,6 +12,11 @@ StyleguideConfig {
"oas3_0": {},
"oas3_1": {},
},
"async3": {
"oas2": {},
"oas3_0": {},
"oas3_1": {},
},
"oas2": {
"oas2": {},
"oas3_0": {},
@@ -51,6 +56,11 @@ StyleguideConfig {
"oas3_0": {},
"oas3_1": {},
},
"async3": {
"oas2": {},
"oas3_0": {},
"oas3_1": {},
},
"oas2": {
"oas2": {},
"oas3_0": {},
@@ -124,6 +134,20 @@ StyleguideConfig {
"operation-summary": "error",
},
},
"async3": {
"oas2": {
"no-empty-servers": "error",
"operation-summary": "error",
},
"oas3_0": {
"no-empty-servers": "error",
"operation-summary": "error",
},
"oas3_1": {
"no-empty-servers": "error",
"operation-summary": "error",
},
},
"oas2": {
"oas2": {
"no-empty-servers": "error",

View File

@@ -119,6 +119,7 @@ describe('getMergedConfig', () => {
"decorators": {
"arazzo": {},
"async2": {},
"async3": {},
"oas2": {},
"oas3_0": {},
"oas3_1": {},
@@ -131,6 +132,7 @@ describe('getMergedConfig', () => {
"preprocessors": {
"arazzo": {},
"async2": {},
"async3": {},
"oas2": {},
"oas3_0": {},
"oas3_1": {},
@@ -148,6 +150,9 @@ describe('getMergedConfig', () => {
"async2": {
"operation-summary": "warn",
},
"async3": {
"operation-summary": "warn",
},
"oas2": {
"operation-summary": "warn",
},
@@ -227,6 +232,7 @@ describe('getMergedConfig', () => {
"decorators": {
"arazzo": {},
"async2": {},
"async3": {},
"oas2": {},
"oas3_0": {},
"oas3_1": {},
@@ -239,6 +245,7 @@ describe('getMergedConfig', () => {
"preprocessors": {
"arazzo": {},
"async2": {},
"async3": {},
"oas2": {},
"oas3_0": {},
"oas3_1": {},
@@ -259,6 +266,10 @@ describe('getMergedConfig', () => {
"no-empty-servers": "error",
"operation-summary": "error",
},
"async3": {
"no-empty-servers": "error",
"operation-summary": "error",
},
"oas2": {
"no-empty-servers": "error",
"operation-summary": "error",

View File

@@ -114,6 +114,15 @@ const all: PluginStyleguideConfig<'built-in'> = {
'channels-kebab-case': 'error',
'no-channel-trailing-slash': 'error',
},
async3Rules: {
spec: 'error',
'info-contact': 'error',
'operation-operationId': 'error',
'tag-description': 'error',
'tags-alphabetical': 'error',
'channels-kebab-case': 'error',
'no-channel-trailing-slash': 'error',
},
arazzoRules: { spec: 'error' },
};

View File

@@ -5,14 +5,17 @@ import minimal from './minimal';
import { rules as oas3Rules } from '../rules/oas3';
import { rules as oas2Rules } from '../rules/oas2';
import { rules as async2Rules } from '../rules/async2';
import { rules as async3Rules } from '../rules/async3';
import { rules as arazzoRules } from '../rules/arazzo';
import { preprocessors as oas3Preprocessors } from '../rules/oas3';
import { preprocessors as oas2Preprocessors } from '../rules/oas2';
import { preprocessors as async2Preprocessors } from '../rules/async2';
import { preprocessors as async3Preprocessors } from '../rules/async3';
import { preprocessors as arazzoPreprocessors } from '../rules/arazzo';
import { decorators as oas3Decorators } from '../decorators/oas3';
import { decorators as oas2Decorators } from '../decorators/oas2';
import { decorators as async2Decorators } from '../decorators/async2';
import { decorators as async3Decorators } from '../decorators/async3';
import { decorators as arazzoDecorators } from '../decorators/arazzo';
import type { CustomRulesConfig, StyleguideRawConfig, Plugin } from './types';
@@ -33,18 +36,21 @@ export const defaultPlugin: Plugin = {
oas3: oas3Rules,
oas2: oas2Rules,
async2: async2Rules,
async3: async3Rules,
arazzo: arazzoRules,
} as CustomRulesConfig,
preprocessors: {
oas3: oas3Preprocessors,
oas2: oas2Preprocessors,
async2: async2Preprocessors,
async3: async3Preprocessors,
arazzo: arazzoPreprocessors,
},
decorators: {
oas3: oas3Decorators,
oas2: oas2Decorators,
async2: async2Decorators,
async3: async3Decorators,
arazzo: arazzoDecorators,
},
configs: builtInConfigs,

View File

@@ -181,7 +181,7 @@ export function resolvePlugins(
if (pluginModule.rules) {
if (!pluginModule.rules.oas3 && !pluginModule.rules.oas2 && !pluginModule.rules.async2) {
throw new Error(
`Plugin rules must have \`oas3\`, \`oas2\`, \`async2\`, or \`arazzo\` rules "${p}.`
`Plugin rules must have \`oas3\`, \`oas2\`, \`async2\`, \`async3\` or \`arazzo\` rules "${p}.`
);
}
plugin.rules = {};
@@ -194,6 +194,9 @@ export function resolvePlugins(
if (pluginModule.rules.async2) {
plugin.rules.async2 = prefixRules(pluginModule.rules.async2, id);
}
if (pluginModule.rules.async3) {
plugin.rules.async3 = prefixRules(pluginModule.rules.async3, id);
}
if (pluginModule.rules.arazzo) {
plugin.rules.arazzo = prefixRules(pluginModule.rules.arazzo, id);
}
@@ -203,6 +206,7 @@ export function resolvePlugins(
!pluginModule.preprocessors.oas3 &&
!pluginModule.preprocessors.oas2 &&
!pluginModule.preprocessors.async2 &&
!pluginModule.preprocessors.async3 &&
!pluginModule.preprocessors.arazzo
) {
throw new Error(
@@ -219,6 +223,9 @@ export function resolvePlugins(
if (pluginModule.preprocessors.async2) {
plugin.preprocessors.async2 = prefixRules(pluginModule.preprocessors.async2, id);
}
if (pluginModule.preprocessors.async3) {
plugin.preprocessors.async3 = prefixRules(pluginModule.preprocessors.async3, id);
}
if (pluginModule.preprocessors.arazzo) {
plugin.preprocessors.arazzo = prefixRules(pluginModule.preprocessors.arazzo, id);
}
@@ -229,10 +236,11 @@ export function resolvePlugins(
!pluginModule.decorators.oas3 &&
!pluginModule.decorators.oas2 &&
!pluginModule.decorators.async2 &&
!pluginModule.decorators.async3 &&
!pluginModule.decorators.arazzo
) {
throw new Error(
`Plugin \`decorators\` must have \`oas3\`, \`oas2\` or \`async2\` decorators "${p}.`
`Plugin \`decorators\` must have \`oas3\`, \`oas2\`, \`async2\` or \`async3\` decorators "${p}.`
);
}
plugin.decorators = {};
@@ -245,6 +253,9 @@ export function resolvePlugins(
if (pluginModule.decorators.async2) {
plugin.decorators.async2 = prefixRules(pluginModule.decorators.async2, id);
}
if (pluginModule.decorators.async3) {
plugin.decorators.async3 = prefixRules(pluginModule.decorators.async3, id);
}
if (pluginModule.decorators.arazzo) {
plugin.decorators.arazzo = prefixRules(pluginModule.decorators.arazzo, id);
}
@@ -432,6 +443,8 @@ function getMergedRawStyleguideConfig(
oas2Rules: { ...rootStyleguideConfig?.oas2Rules, ...apiStyleguideConfig?.oas2Rules },
oas3_0Rules: { ...rootStyleguideConfig?.oas3_0Rules, ...apiStyleguideConfig?.oas3_0Rules },
oas3_1Rules: { ...rootStyleguideConfig?.oas3_1Rules, ...apiStyleguideConfig?.oas3_1Rules },
async2Rules: { ...rootStyleguideConfig?.async2Rules, ...apiStyleguideConfig?.async2Rules },
async3Rules: { ...rootStyleguideConfig?.async3Rules, ...apiStyleguideConfig?.async3Rules },
arazzoRules: { ...rootStyleguideConfig?.arazzoRules, ...apiStyleguideConfig?.arazzoRules },
preprocessors: {
...rootStyleguideConfig?.preprocessors,

View File

@@ -9,6 +9,7 @@ import {
Oas2RuleSet,
Oas3RuleSet,
Async2RuleSet,
Async3RuleSet,
ArazzoRuleSet,
} from '../oas-types';
import { isBrowser } from '../env';
@@ -72,6 +73,7 @@ export class StyleguideConfig {
[SpecVersion.OAS3_0]: { ...rawConfig.rules, ...rawConfig.oas3_0Rules },
[SpecVersion.OAS3_1]: { ...rawConfig.rules, ...rawConfig.oas3_1Rules },
[SpecVersion.Async2]: { ...rawConfig.rules, ...rawConfig.async2Rules },
[SpecVersion.Async3]: { ...rawConfig.rules, ...rawConfig.async3Rules },
[SpecVersion.Arazzo]: { ...rawConfig.arazzoRules },
};
@@ -80,6 +82,7 @@ export class StyleguideConfig {
[SpecVersion.OAS3_0]: { ...rawConfig.preprocessors, ...rawConfig.oas3_0Preprocessors },
[SpecVersion.OAS3_1]: { ...rawConfig.preprocessors, ...rawConfig.oas3_1Preprocessors },
[SpecVersion.Async2]: { ...rawConfig.preprocessors, ...rawConfig.async2Preprocessors },
[SpecVersion.Async3]: { ...rawConfig.preprocessors, ...rawConfig.async3Preprocessors },
[SpecVersion.Arazzo]: { ...rawConfig.arazzoPreprocessors },
};
@@ -88,6 +91,7 @@ export class StyleguideConfig {
[SpecVersion.OAS3_0]: { ...rawConfig.decorators, ...rawConfig.oas3_0Decorators },
[SpecVersion.OAS3_1]: { ...rawConfig.decorators, ...rawConfig.oas3_1Decorators },
[SpecVersion.Async2]: { ...rawConfig.decorators, ...rawConfig.async2Decorators },
[SpecVersion.Async3]: { ...rawConfig.decorators, ...rawConfig.async3Decorators },
[SpecVersion.Arazzo]: { ...rawConfig.arazzoDecorators },
};
@@ -182,6 +186,10 @@ export class StyleguideConfig {
if (!plugin.typeExtension.async2) continue;
extendedTypes = plugin.typeExtension.async2(extendedTypes, version);
break;
case SpecVersion.Async3:
if (!plugin.typeExtension.async3) continue;
extendedTypes = plugin.typeExtension.async3(extendedTypes, version);
break;
case SpecVersion.Arazzo:
if (!plugin.typeExtension.arazzo) continue;
extendedTypes = plugin.typeExtension.arazzo(extendedTypes, version);
@@ -276,15 +284,26 @@ export class StyleguideConfig {
return oas2Rules;
case SpecMajorVersion.Async2:
// eslint-disable-next-line no-case-declarations
const asyncApiRules: Async2RuleSet[] = []; // default ruleset
const asyncApi2Rules: Async2RuleSet[] = []; // default ruleset
this.plugins.forEach(
(p) => p.preprocessors?.async2 && asyncApiRules.push(p.preprocessors.async2)
(p) => p.preprocessors?.async2 && asyncApi2Rules.push(p.preprocessors.async2)
);
this.plugins.forEach((p) => p.rules?.async2 && asyncApiRules.push(p.rules.async2));
this.plugins.forEach((p) => p.rules?.async2 && asyncApi2Rules.push(p.rules.async2));
this.plugins.forEach(
(p) => p.decorators?.async2 && asyncApiRules.push(p.decorators.async2)
(p) => p.decorators?.async2 && asyncApi2Rules.push(p.decorators.async2)
);
return asyncApiRules;
return asyncApi2Rules;
case SpecMajorVersion.Async3:
// eslint-disable-next-line no-case-declarations
const asyncApi3Rules: Async3RuleSet[] = []; // default ruleset
this.plugins.forEach(
(p) => p.preprocessors?.async3 && asyncApi3Rules.push(p.preprocessors.async3)
);
this.plugins.forEach((p) => p.rules?.async3 && asyncApi3Rules.push(p.rules.async3));
this.plugins.forEach(
(p) => p.decorators?.async3 && asyncApi3Rules.push(p.decorators.async3)
);
return asyncApi3Rules;
case SpecMajorVersion.Arazzo:
// eslint-disable-next-line no-case-declarations
const arazzoRules: ArazzoRuleSet[] = []; // default ruleset

View File

@@ -96,6 +96,15 @@ const minimal: PluginStyleguideConfig<'built-in'> = {
'channels-kebab-case': 'off',
'no-channel-trailing-slash': 'off',
},
async3Rules: {
spec: 'error',
'info-contact': 'off',
'operation-operationId': 'warn',
'tag-description': 'warn',
'tags-alphabetical': 'off',
'channels-kebab-case': 'off',
'no-channel-trailing-slash': 'off',
},
arazzoRules: {
spec: 'error',
},

View File

@@ -96,6 +96,15 @@ const recommendedStrict: PluginStyleguideConfig<'built-in'> = {
'channels-kebab-case': 'off',
'no-channel-trailing-slash': 'off',
},
async3Rules: {
spec: 'error',
'info-contact': 'off',
'operation-operationId': 'error',
'tag-description': 'error',
'tags-alphabetical': 'off',
'channels-kebab-case': 'off',
'no-channel-trailing-slash': 'off',
},
arazzoRules: {
spec: 'error',
},

View File

@@ -96,6 +96,15 @@ const recommended: PluginStyleguideConfig<'built-in'> = {
'channels-kebab-case': 'off',
'no-channel-trailing-slash': 'off',
},
async3Rules: {
spec: 'error',
'info-contact': 'off',
'operation-operationId': 'warn',
'tag-description': 'warn',
'tags-alphabetical': 'off',
'channels-kebab-case': 'off',
'no-channel-trailing-slash': 'off',
},
arazzoRules: {
spec: 'error',
},

View File

@@ -11,6 +11,9 @@ import type {
Async2PreprocessorsSet,
Async2DecoratorsSet,
Async2RuleSet,
Async3PreprocessorsSet,
Async3DecoratorsSet,
Async3RuleSet,
ArazzoRuleSet,
ArazzoPreprocessorsSet,
ArazzoDecoratorsSet,
@@ -22,6 +25,7 @@ import { Location } from '../ref-utils';
import type { SkipFunctionContext } from '../visitors';
import {
BuiltInAsync2RuleId,
BuiltInAsync3RuleId,
BuiltInCommonOASRuleId,
BuiltInOAS2RuleId,
BuiltInOAS3RuleId,
@@ -59,6 +63,7 @@ export type StyleguideRawConfig<T = undefined> = {
oas3_0Rules?: RuleMap<BuiltInOAS3RuleId, RuleConfig, T>;
oas3_1Rules?: RuleMap<BuiltInOAS3RuleId, RuleConfig, T>;
async2Rules?: RuleMap<BuiltInAsync2RuleId, RuleConfig, T>;
async3Rules?: RuleMap<BuiltInAsync3RuleId, RuleConfig, T>;
arazzoRules?: RuleMap<BuiltInArazzoRuleId, RuleConfig, T>;
preprocessors?: Record<string, PreprocessorConfig>;
@@ -66,6 +71,7 @@ export type StyleguideRawConfig<T = undefined> = {
oas3_0Preprocessors?: Record<string, PreprocessorConfig>;
oas3_1Preprocessors?: Record<string, PreprocessorConfig>;
async2Preprocessors?: Record<string, PreprocessorConfig>;
async3Preprocessors?: Record<string, PreprocessorConfig>;
arazzoPreprocessors?: Record<string, PreprocessorConfig>;
decorators?: Record<string, DecoratorConfig>;
@@ -73,6 +79,7 @@ export type StyleguideRawConfig<T = undefined> = {
oas3_0Decorators?: Record<string, DecoratorConfig>;
oas3_1Decorators?: Record<string, DecoratorConfig>;
async2Decorators?: Record<string, DecoratorConfig>;
async3Decorators?: Record<string, DecoratorConfig>;
arazzoDecorators?: Record<string, DecoratorConfig>;
};
@@ -90,6 +97,7 @@ export type PreprocessorsConfig = {
oas3?: Oas3PreprocessorsSet;
oas2?: Oas2PreprocessorsSet;
async2?: Async2PreprocessorsSet;
async3?: Async3PreprocessorsSet;
arazzo?: ArazzoPreprocessorsSet;
};
@@ -97,6 +105,7 @@ export type DecoratorsConfig = {
oas3?: Oas3DecoratorsSet;
oas2?: Oas2DecoratorsSet;
async2?: Async2DecoratorsSet;
async3?: Async3DecoratorsSet;
arazzo?: ArazzoDecoratorsSet;
};
@@ -111,6 +120,7 @@ export type CustomRulesConfig = {
oas3?: Oas3RuleSet;
oas2?: Oas2RuleSet;
async2?: Async2RuleSet;
async3?: Async3RuleSet;
arazzo?: ArazzoRuleSet;
};
@@ -243,16 +253,19 @@ export type RulesFields =
| 'oas3_0Rules'
| 'oas3_1Rules'
| 'async2Rules'
| 'async3Rules'
| 'arazzoRules'
| 'preprocessors'
| 'oas2Preprocessors'
| 'oas3_0Preprocessors'
| 'oas3_1Preprocessors'
| 'async2Preprocessors'
| 'async3Preprocessors'
| 'arazzoPreprocessors'
| 'decorators'
| 'oas2Decorators'
| 'oas3_0Decorators'
| 'oas3_1Decorators'
| 'arazzoDecorators'
| 'async2Decorators';
| 'async2Decorators'
| 'async3Decorators'
| 'arazzoDecorators';

View File

@@ -56,6 +56,7 @@ function extractFlatConfig<
oas3_0Rules,
oas3_1Rules,
async2Rules,
async3Rules,
arazzoRules,
preprocessors,
@@ -63,6 +64,7 @@ function extractFlatConfig<
oas3_0Preprocessors,
oas3_1Preprocessors,
async2Preprocessors,
async3Preprocessors,
arazzoPreprocessors,
decorators,
@@ -70,6 +72,7 @@ function extractFlatConfig<
oas3_0Decorators,
oas3_1Decorators,
async2Decorators,
async3Decorators,
arazzoDecorators,
...rawConfigRest
@@ -86,6 +89,7 @@ function extractFlatConfig<
oas3_0Rules,
oas3_1Rules,
async2Rules,
async3Rules,
arazzoRules,
preprocessors,
@@ -93,6 +97,7 @@ function extractFlatConfig<
oas3_0Preprocessors,
oas3_1Preprocessors,
async2Preprocessors,
async3Preprocessors,
arazzoPreprocessors,
decorators,
@@ -100,6 +105,7 @@ function extractFlatConfig<
oas3_0Decorators,
oas3_1Decorators,
async2Decorators,
async3Decorators,
arazzoDecorators,
doNotResolveExamples: rawConfigRest.resolve?.doNotResolveExamples,
@@ -158,6 +164,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
oas3_0Rules: {},
oas3_1Rules: {},
async2Rules: {},
async3Rules: {},
arazzoRules: {},
preprocessors: {},
@@ -165,6 +172,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
oas3_0Preprocessors: {},
oas3_1Preprocessors: {},
async2Preprocessors: {},
async3Preprocessors: {},
arazzoPreprocessors: {},
decorators: {},
@@ -172,6 +180,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
oas3_0Decorators: {},
oas3_1Decorators: {},
async2Decorators: {},
async3Decorators: {},
arazzoDecorators: {},
plugins: [],
@@ -195,6 +204,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
assignExisting(result.oas3_1Rules, rulesConf.rules || {});
Object.assign(result.async2Rules, rulesConf.async2Rules);
assignExisting(result.async2Rules, rulesConf.rules || {});
Object.assign(result.async3Rules, rulesConf.async3Rules);
assignExisting(result.async3Rules, rulesConf.rules || {});
Object.assign(result.arazzoRules, rulesConf.arazzoRules);
assignExisting(result.arazzoRules, rulesConf.rules || {});
@@ -207,6 +218,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
assignExisting(result.oas3_1Preprocessors, rulesConf.preprocessors || {});
Object.assign(result.async2Preprocessors, rulesConf.async2Preprocessors);
assignExisting(result.async2Preprocessors, rulesConf.preprocessors || {});
Object.assign(result.async3Preprocessors, rulesConf.async3Preprocessors);
assignExisting(result.async3Preprocessors, rulesConf.preprocessors || {});
Object.assign(result.arazzoPreprocessors, rulesConf.arazzoPreprocessors);
assignExisting(result.arazzoPreprocessors, rulesConf.preprocessors || {});
@@ -219,6 +232,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
assignExisting(result.oas3_1Decorators, rulesConf.decorators || {});
Object.assign(result.async2Decorators, rulesConf.async2Decorators);
assignExisting(result.async2Decorators, rulesConf.decorators || {});
Object.assign(result.async3Decorators, rulesConf.async3Decorators);
assignExisting(result.async3Decorators, rulesConf.decorators || {});
Object.assign(result.arazzoDecorators, rulesConf.arazzoDecorators);
assignExisting(result.arazzoDecorators, rulesConf.decorators || {});

View File

@@ -0,0 +1 @@
export const decorators = {};

View File

@@ -11,7 +11,8 @@ export { Oas3_1Types } from './types/oas3_1';
export { ArazzoTypes } from './types/arazzo';
export { Oas3Types } from './types/oas3';
export { Oas2Types } from './types/oas2';
export { AsyncApi2Types } from './types/asyncapi';
export { AsyncApi2Types } from './types/asyncapi2';
export { AsyncApi3Types } from './types/asyncapi3';
export { ConfigTypes } from './types/redocly-yaml';
export type {
Oas3Definition,

View File

@@ -5,16 +5,20 @@ import {
Oas2Preprocessor,
Async2Preprocessor,
Async2Rule,
Async3Preprocessor,
Async3Rule,
ArazzoPreprocessor,
ArazzoRule,
} from './visitors';
import { Oas2Types } from './types/oas2';
import { Oas3Types } from './types/oas3';
import { Oas3_1Types } from './types/oas3_1';
import { AsyncApi2Types } from './types/asyncapi';
import { AsyncApi2Types } from './types/asyncapi2';
import { AsyncApi3Types } from './types/asyncapi3';
import { ArazzoTypes } from './types/arazzo';
import {
BuiltInAsync2RuleId,
BuiltInAsync3RuleId,
BuiltInCommonOASRuleId,
BuiltInArazzoRuleId,
BuiltInOAS2RuleId,
@@ -27,7 +31,8 @@ export enum SpecVersion {
OAS2 = 'oas2',
OAS3_0 = 'oas3_0',
OAS3_1 = 'oas3_1',
Async2 = 'async2', // todo split into 2.x maybe?
Async2 = 'async2',
Async3 = 'async3',
Arazzo = 'arazzo',
}
@@ -35,6 +40,7 @@ export enum SpecMajorVersion {
OAS2 = 'oas2',
OAS3 = 'oas3',
Async2 = 'async2',
Async3 = 'async3',
Arazzo = 'arazzo',
}
@@ -43,6 +49,7 @@ const typesMap = {
[SpecVersion.OAS3_0]: Oas3Types,
[SpecVersion.OAS3_1]: Oas3_1Types,
[SpecVersion.Async2]: AsyncApi2Types,
[SpecVersion.Async3]: AsyncApi3Types,
[SpecVersion.Arazzo]: ArazzoTypes,
};
@@ -65,6 +72,11 @@ export type Async2RuleSet<T = undefined> = RuleMap<
Async2Rule,
T
>;
export type Async3RuleSet<T = undefined> = RuleMap<
BuiltInAsync3RuleId | 'assertions',
Async3Rule,
T
>;
export type ArazzoRuleSet<T = undefined> = RuleMap<
BuiltInArazzoRuleId | 'assertions',
ArazzoRule,
@@ -74,11 +86,13 @@ export type ArazzoRuleSet<T = undefined> = RuleMap<
export type Oas3PreprocessorsSet = Record<string, Oas3Preprocessor>;
export type Oas2PreprocessorsSet = Record<string, Oas2Preprocessor>;
export type Async2PreprocessorsSet = Record<string, Async2Preprocessor>;
export type Async3PreprocessorsSet = Record<string, Async3Preprocessor>;
export type ArazzoPreprocessorsSet = Record<string, ArazzoPreprocessor>;
export type Oas3DecoratorsSet = Record<string, Oas3Preprocessor>;
export type Oas2DecoratorsSet = Record<string, Oas2Preprocessor>;
export type Async2DecoratorsSet = Record<string, Async2Preprocessor>;
export type Async3DecoratorsSet = Record<string, Async3Preprocessor>;
export type ArazzoDecoratorsSet = Record<string, ArazzoPreprocessor>;
export function detectSpec(root: any): SpecVersion {
@@ -111,6 +125,10 @@ export function detectSpec(root: any): SpecVersion {
return SpecVersion.Async2;
}
if (root.asyncapi && root.asyncapi.startsWith('3.')) {
return SpecVersion.Async3;
}
if (root.asyncapi) {
throw new Error(`Unsupported AsyncAPI version: ${root.asyncapi}`);
}
@@ -127,6 +145,8 @@ export function getMajorSpecVersion(version: SpecVersion): SpecMajorVersion {
return SpecMajorVersion.OAS2;
} else if (version === SpecVersion.Async2) {
return SpecMajorVersion.Async2;
} else if (version === SpecVersion.Async3) {
return SpecMajorVersion.Async3;
} else if (version === SpecVersion.Arazzo) {
return SpecMajorVersion.Arazzo;
} else {

View File

@@ -0,0 +1,141 @@
import { outdent } from 'outdent';
import { lintDocument } from '../../../lint';
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
import { BaseResolver } from '../../../resolve';
describe('Async2 channels-kebab-case', () => {
it('should report on no kebab-case channel path', async () => {
const document = parseYamlToDocument(
outdent`
asyncapi: '3.0.0'
info:
title: Cool API
version: 1.0.0
channels:
channel1:
address: /NOT_A_KEBAB/
payload:
type: object
`,
'asyncapi.yaml'
);
const results = await lintDocument({
externalRefResolver: new BaseResolver(),
document,
config: await makeConfig({ 'channels-kebab-case': 'error' }),
});
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
[
{
"location": [
{
"pointer": "#/channels/channel1",
"reportOnKey": true,
"source": "asyncapi.yaml",
},
],
"message": "\`/NOT_A_KEBAB/\` does not use kebab-case.",
"ruleId": "channels-kebab-case",
"severity": "error",
"suggest": [],
},
]
`);
});
it('should report on snake_case in channel path', async () => {
const document = parseYamlToDocument(
outdent`
asyncapi: '3.0.0'
info:
title: Cool API
version: 1.0.0
channels:
channel1:
address: snake_kebab
payload:
type: object
`,
'asyncapi.yaml'
);
const results = await lintDocument({
externalRefResolver: new BaseResolver(),
document,
config: await makeConfig({ 'channels-kebab-case': 'error' }),
});
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
[
{
"location": [
{
"pointer": "#/channels/channel1",
"reportOnKey": true,
"source": "asyncapi.yaml",
},
],
"message": "\`snake_kebab\` does not use kebab-case.",
"ruleId": "channels-kebab-case",
"severity": "error",
"suggest": [],
},
]
`);
});
it('should allow trailing slash in channel path with "channels-kebab-case" rule', async () => {
const document = parseYamlToDocument(
outdent`
asyncapi: '3.0.0'
info:
title: Cool API
version: 1.0.0
channels:
channel1:
address: kebab/
payload:
type: object
`,
'asyncapi.yaml'
);
const results = await lintDocument({
externalRefResolver: new BaseResolver(),
document,
config: await makeConfig({
'paths-kebab-case': 'error',
'no-path-trailing-slash': 'off',
}),
});
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
});
it('words with hyphens are allowed with "channels-kebab-case" rule', async () => {
const document = parseYamlToDocument(
outdent`
asyncapi: '3.0.0'
info:
title: Cool API
version: 1.0.0
channels:
channel1:
address: kebab-with-longer-channel-path:/
payload:
type: object
`,
'asyncapi.yaml'
);
const results = await lintDocument({
externalRefResolver: new BaseResolver(),
document,
config: await makeConfig({
'paths-kebab-case': 'error',
}),
});
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
});
});

View File

@@ -0,0 +1,96 @@
import { outdent } from 'outdent';
import { lintDocument } from '../../../lint';
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
import { BaseResolver } from '../../../resolve';
describe('no-channel-trailing-slash', () => {
it.only('should report on trailing slash in a channel path', async () => {
const document = parseYamlToDocument(
outdent`
asyncapi: '3.0.0'
info:
title: Excellent API
version: 1.0.0
channels:
channel1:
address: /trailing/
payload:
type: object
`,
'asyncapi.yaml'
);
const results = await lintDocument({
externalRefResolver: new BaseResolver(),
document,
config: await makeConfig({ 'no-channel-trailing-slash': 'error' }),
});
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
[
{
"location": [
{
"pointer": "#/channels/channel1",
"reportOnKey": true,
"source": "asyncapi.yaml",
},
],
"message": "\`/trailing/\` should not have a trailing slash.",
"ruleId": "no-channel-trailing-slash",
"severity": "error",
"suggest": [],
},
]
`);
});
it('should not report on if no trailing slash in path', async () => {
const document = parseYamlToDocument(
outdent`
asyncapi: '3.0.0'
info:
title: Excellent API
version: 1.0.0
channels:
channel1:
address: /expected
payload:
type: object
`,
'asyncapi.yaml'
);
const results = await lintDocument({
externalRefResolver: new BaseResolver(),
document,
config: await makeConfig({ 'no-channel-trailing-slash': 'error' }),
});
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
});
it('should not report on trailing slash in path if the path is root', async () => {
const document = parseYamlToDocument(
outdent`
asyncapi: '3.0.0'
info:
title: Excellent API
version: 1.0.0
channels:
channel1:
address: /
payload:
type: object
`,
'foobar.yaml'
);
const results = await lintDocument({
externalRefResolver: new BaseResolver(),
document,
config: await makeConfig({ 'no-channel-trailing-slash': 'error' }),
});
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
});
});

View File

@@ -0,0 +1,19 @@
import { Channel } from '../../typings/asyncapi3';
import { Async3Rule } from '../../visitors';
import { UserContext } from '../../walk';
export const ChannelsKebabCase: Async3Rule = () => {
return {
Channel(channel: Channel, { report }: UserContext) {
const segments = (channel.address || '')
.split(/[/.:]/) // split on / or : as likely channel namespacers
.filter((s) => s !== ''); // filter out empty segments
if (!segments.every((segment) => /^{.+}$/.test(segment) || /^[a-z0-9-.]+$/.test(segment))) {
report({
message: `\`${channel.address}\` does not use kebab-case.`,
location: { reportOnKey: true },
});
}
},
};
};

View File

@@ -0,0 +1,23 @@
import { Async3Rule } from '../../visitors';
import { Assertions } from '../common/assertions';
import { Spec } from '../common/spec';
import { InfoContact } from '../common/info-contact';
import { OperationOperationId } from '../common/operation-operationId';
import { TagDescription } from '../common/tag-description';
import { TagsAlphabetical } from '../common/tags-alphabetical';
import { ChannelsKebabCase } from './channels-kebab-case';
import { NoChannelTrailingSlash } from './no-channel-trailing-slash';
import type { Async3RuleSet } from '../../oas-types';
export const rules: Async3RuleSet<'built-in'> = {
spec: Spec as Async3Rule,
assertions: Assertions,
'info-contact': InfoContact,
'operation-operationId': OperationOperationId,
'channels-kebab-case': ChannelsKebabCase,
'no-channel-trailing-slash': NoChannelTrailingSlash,
'tag-description': TagDescription,
'tags-alphabetical': TagsAlphabetical,
};
export const preprocessors = {};

View File

@@ -0,0 +1,16 @@
import { Async3Rule } from '../../visitors';
import { UserContext } from '../../walk';
import { Channel } from '../../typings/asyncapi3';
export const NoChannelTrailingSlash: Async3Rule = () => {
return {
Channel(channel: Channel, { report, location }: UserContext) {
if ((channel.address as string).endsWith('/') && channel.address !== '/') {
report({
message: `\`${channel.address}\` should not have a trailing slash.`,
location: location.key(),
});
}
},
};
};

View File

@@ -1,6 +1,6 @@
import { asserts, AssertionFn } from './asserts';
import { buildSubjectVisitor, buildVisitorObject } from './utils';
import { Oas2Visitor, Oas3Visitor } from '../../../visitors';
import { Async2Visitor, Async3Visitor, Oas2Visitor, Oas3Visitor } from '../../../visitors';
import { RuleSeverity } from '../../../config';
import { isString } from '../../../utils';
@@ -28,7 +28,7 @@ export type RawAssertion = AssertionDefinition & {
export type Assertion = RawAssertion & { assertionId: string };
export const Assertions = (opts: Record<string, Assertion>) => {
const visitors: (Oas2Visitor | Oas3Visitor)[] = [];
const visitors: (Oas2Visitor | Oas3Visitor | Async2Visitor | Async3Visitor)[] = [];
// As 'Assertions' has an array of asserts,
// that array spreads into an 'opts' object on init rules phase here

View File

@@ -1,11 +1,11 @@
import type { Oas3Rule, Oas2Rule, Async2Rule, ArazzoRule } from '../../visitors';
import type { Oas3Rule, Oas2Rule, Async2Rule, Async3Rule, ArazzoRule } from '../../visitors';
import { isNamedType, SpecExtension } from '../../types';
import { oasTypeOf, matchesJsonSchemaType, getSuggest, validateSchemaEnumType } from '../utils';
import { isRef } from '../../ref-utils';
import { isPlainObject } from '../../utils';
import { UserContext } from '../../walk';
export const Spec: Oas3Rule | Oas2Rule | Async2Rule | ArazzoRule = () => {
export const Spec: Oas3Rule | Oas2Rule | Async2Rule | Async3Rule | ArazzoRule = () => {
return {
any(
node: any,

View File

@@ -61,7 +61,7 @@ const ChannelBindings: NodeType = {
additionalProperties: { type: 'object' },
};
const Tag: NodeType = {
export const Tag: NodeType = {
properties: {
name: { type: 'string' },
description: { type: 'string' },
@@ -70,7 +70,7 @@ const Tag: NodeType = {
required: ['name'],
};
const ExternalDocs: NodeType = {
export const ExternalDocs: NodeType = {
properties: {
description: { type: 'string' },
url: { type: 'string' },
@@ -126,13 +126,13 @@ const Server: NodeType = {
required: ['url', 'protocol'],
};
const ServerMap: NodeType = {
export const ServerMap: NodeType = {
properties: {},
additionalProperties: (_value: any, key: string) =>
key.match(/^[A-Za-z0-9_\-]+$/) ? 'Server' : undefined,
};
const ServerVariable: NodeType = {
export const ServerVariable: NodeType = {
properties: {
enum: {
type: 'array',
@@ -160,7 +160,7 @@ const Info: NodeType = {
required: ['title', 'version'],
};
const Contact: NodeType = {
export const Contact: NodeType = {
properties: {
name: { type: 'string' },
url: { type: 'string' },
@@ -168,7 +168,7 @@ const Contact: NodeType = {
},
};
const License: NodeType = {
export const License: NodeType = {
properties: {
name: { type: 'string' },
url: { type: 'string' },
@@ -184,7 +184,7 @@ const Parameter: NodeType = {
},
};
const CorrelationId: NodeType = {
export const CorrelationId: NodeType = {
properties: {
description: { type: 'string' },
location: { type: 'string' },
@@ -208,7 +208,7 @@ const Message: NodeType = {
tags: 'TagList',
externalDocs: 'ExternalDocs',
bindings: 'MessageBindings',
// examples: 'MessageExampleList', // TODO: add support for message examples
examples: 'MessageExampleList',
traits: 'MessageTraitList',
},
additionalProperties: {},
@@ -301,7 +301,7 @@ const MessageTrait: NodeType = {
tags: 'TagList',
externalDocs: 'ExternalDocs',
bindings: 'MessageBindings',
// examples: 'MessageExampleList', // TODO: support examples for message traits
examples: 'MessageExampleList',
},
additionalProperties: {},
};
@@ -322,7 +322,7 @@ const Operation: NodeType = {
required: [],
};
const MessageExample: NodeType = {
export const MessageExample: NodeType = {
properties: {
payload: { isExample: true },
summary: { type: 'string' },
@@ -331,7 +331,7 @@ const MessageExample: NodeType = {
},
};
const Schema: NodeType = {
export const Schema: NodeType = {
properties: {
$id: { type: 'string' },
$schema: { type: 'string' },
@@ -401,14 +401,14 @@ const Schema: NodeType = {
},
};
const SchemaProperties: NodeType = {
export const SchemaProperties: NodeType = {
properties: {},
additionalProperties: (value: any) => {
return typeof value === 'boolean' ? { type: 'boolean' } : 'Schema';
},
};
const DiscriminatorMapping: NodeType = {
export const DiscriminatorMapping: NodeType = {
properties: {},
additionalProperties: (value: any) => {
if (isMappingRef(value)) {
@@ -419,7 +419,7 @@ const DiscriminatorMapping: NodeType = {
},
};
const Discriminator: NodeType = {
export const Discriminator: NodeType = {
properties: {
propertyName: { type: 'string' },
mapping: 'DiscriminatorMapping',
@@ -435,7 +435,6 @@ const Components: NodeType = {
correlationIds: 'NamedCorrelationIds',
messageTraits: 'NamedMessageTraits',
operationTraits: 'NamedOperationTraits',
streamHeaders: 'NamedStreamHeaders',
securitySchemes: 'NamedSecuritySchemes',
servers: 'ServerMap',
serverVariables: 'ServerVariablesMap',
@@ -484,7 +483,7 @@ const AuthorizationCode: NodeType = {
required: ['authorizationUrl', 'tokenUrl', 'scopes'],
};
const SecuritySchemeFlows: NodeType = {
export const SecuritySchemeFlows: NodeType = {
properties: {
implicit: 'ImplicitFlow',
password: 'PasswordFlow',
@@ -555,7 +554,7 @@ const SecurityScheme: NodeType = {
extensionsPrefix: 'x-',
};
const Dependencies: NodeType = {
export const Dependencies: NodeType = {
properties: {},
additionalProperties: (value: any) => {
return Array.isArray(value) ? { type: 'array', items: { type: 'string' } } : 'Schema';
@@ -987,22 +986,7 @@ OperationBindings.properties.mercure = MercureOperationBinding;
// pulsar
// --- End per-protocol node types
export const AsyncApi2Types: Record<string, NodeType> = {
Root,
Tag,
TagList: listOf('Tag'),
ServerMap,
ExternalDocs,
Server,
ServerVariable,
ServerVariablesMap: mapOf('ServerVariable'),
SecurityRequirement,
SecurityRequirementList: listOf('SecurityRequirement'),
Info,
Contact,
License,
export const AsyncApi2Bindings: Record<string, NodeType> = {
HttpServerBinding,
HttpChannelBinding,
HttpMessageBinding,
@@ -1078,6 +1062,25 @@ export const AsyncApi2Types: Record<string, NodeType> = {
ServerBindings,
ChannelBindings,
MessageBindings,
OperationBindings,
};
export const AsyncApi2Types: Record<string, NodeType> = {
...AsyncApi2Bindings,
Root,
Tag,
TagList: listOf('Tag'),
ServerMap,
ExternalDocs,
Server,
ServerVariable,
ServerVariablesMap: mapOf('ServerVariable'),
SecurityRequirement,
SecurityRequirementList: listOf('SecurityRequirement'),
Info,
Contact,
License,
ChannelMap,
Channel,
Parameter,
@@ -1096,7 +1099,6 @@ export const AsyncApi2Types: Record<string, NodeType> = {
NamedParameters: mapOf('Parameter'),
NamedSecuritySchemes: mapOf('SecurityScheme'),
NamedCorrelationIds: mapOf('CorrelationId'),
NamedStreamHeaders: mapOf('StreamHeader'),
ImplicitFlow,
PasswordFlow,
ClientCredentials,
@@ -1110,6 +1112,7 @@ export const AsyncApi2Types: Record<string, NodeType> = {
OperationTraitList: listOf('OperationTrait'),
MessageTrait,
MessageTraitList: listOf('MessageTrait'),
MessageExampleList: listOf('MessageExample'),
CorrelationId,
Dependencies,
};

View File

@@ -0,0 +1,381 @@
import { NodeType, listOf, mapOf } from '.';
import {
AsyncApi2Bindings,
Schema,
Dependencies,
Discriminator,
DiscriminatorMapping,
SchemaProperties,
CorrelationId,
Tag,
ServerMap,
ExternalDocs,
SecuritySchemeFlows,
ServerVariable,
Contact,
License,
MessageExample,
} from './asyncapi2';
const Root: NodeType = {
properties: {
asyncapi: { type: 'string', enum: ['3.0.0'] },
info: 'Info',
id: { type: 'string' },
servers: 'ServerMap',
channels: 'NamedChannels',
components: 'Components',
operations: 'NamedOperations',
defaultContentType: { type: 'string' },
},
required: ['asyncapi', 'info'],
};
const Channel: NodeType = {
properties: {
address: { type: 'string' },
messages: 'NamedMessages',
title: { type: 'string' },
summary: { type: 'string' },
description: { type: 'string' },
servers: 'ServerList',
parameters: 'ParametersMap',
bindings: 'ChannelBindings',
tags: 'TagList',
externalDocs: 'ExternalDocs',
},
};
const Server: NodeType = {
properties: {
host: { type: 'string' },
pathname: { type: 'string' },
protocol: { type: 'string' },
protocolVersion: { type: 'string' },
description: { type: 'string' },
variables: 'ServerVariablesMap',
security: 'SecuritySchemeList',
bindings: 'ServerBindings',
externalDocs: 'ExternalDocs',
tags: 'TagList',
},
required: ['host', 'protocol'],
};
const Info: NodeType = {
properties: {
title: { type: 'string' },
version: { type: 'string' },
description: { type: 'string' },
termsOfService: { type: 'string' },
contact: 'Contact',
license: 'License',
tags: 'TagList',
externalDocs: 'ExternalDocs',
},
required: ['title', 'version'],
};
const Parameter: NodeType = {
properties: {
description: { type: 'string' },
enum: { type: 'array', items: { type: 'string' } },
default: { type: 'string' },
examples: { type: 'array', items: { type: 'string' } },
location: { type: 'string' },
},
};
const Message: NodeType = {
properties: {
headers: 'Schema',
payload: (value: Record<string, unknown>) => {
if (!!value && value?.['schemaFormat']) {
return {
properties: {
schema: 'Schema',
schemaFormat: { type: 'string' },
},
required: ['schema', 'schemaFormat'],
};
} else {
return 'Schema';
}
},
correlationId: 'CorrelationId',
contentType: { type: 'string' },
name: { type: 'string' },
title: { type: 'string' },
summary: { type: 'string' },
description: { type: 'string' },
tags: 'TagList',
externalDocs: 'ExternalDocs',
bindings: 'MessageBindings',
examples: 'MessageExampleList',
traits: 'MessageTraitList',
},
additionalProperties: {},
};
const OperationTrait: NodeType = {
properties: {
tags: 'TagList',
title: { type: 'string' },
summary: { type: 'string' },
description: { type: 'string' },
externalDocs: 'ExternalDocs',
security: 'SecuritySchemeList',
bindings: 'OperationBindings',
},
required: [],
};
const MessageTrait: NodeType = {
properties: {
headers: (value: unknown) => {
if (typeof value === 'function' || (typeof value === 'object' && !!value)) {
return {
properties: {
schema: 'Schema',
schemaFormat: { type: 'string' },
},
};
} else {
return 'Schema';
}
},
correlationId: 'CorrelationId',
contentType: { type: 'string' },
name: { type: 'string' },
title: { type: 'string' },
summary: { type: 'string' },
description: { type: 'string' },
tags: 'TagList',
externalDocs: 'ExternalDocs',
bindings: 'MessageBindings',
examples: 'MessageExampleList',
},
additionalProperties: {},
};
const Operation: NodeType = {
properties: {
action: { type: 'string', enum: ['send', 'receive'] },
channel: 'Channel',
title: { type: 'string' },
tags: 'TagList',
summary: { type: 'string' },
description: { type: 'string' },
externalDocs: 'ExternalDocs',
operationId: { type: 'string' },
security: 'SecuritySchemeList',
bindings: 'OperationBindings',
traits: 'OperationTraitList',
messages: 'MessageList',
reply: 'OperationReply',
},
required: ['action', 'channel'],
};
const OperationReply: NodeType = {
properties: {
channel: 'Channel',
messages: 'MessageList',
address: 'OperationReplyAddress',
},
};
const OperationReplyAddress: NodeType = {
properties: {
location: { type: 'string' },
description: { type: 'string' },
},
required: ['location'],
};
const Components: NodeType = {
properties: {
messages: 'NamedMessages',
parameters: 'NamedParameters',
schemas: 'NamedSchemas',
replies: 'NamedOperationReplies',
replyAddresses: 'NamedOperationRelyAddresses',
correlationIds: 'NamedCorrelationIds',
messageTraits: 'NamedMessageTraits',
operationTraits: 'NamedOperationTraits',
tags: 'NamedTags',
externalDocs: 'NamedExternalDocs',
securitySchemes: 'NamedSecuritySchemes',
servers: 'ServerMap',
serverVariables: 'ServerVariablesMap',
channels: 'NamedChannels',
operations: 'NamedOperations',
serverBindings: 'ServerBindings',
channelBindings: 'ChannelBindings',
operationBindings: 'OperationBindings',
messageBindings: 'MessageBindings',
},
};
const ImplicitFlow: NodeType = {
properties: {
refreshUrl: { type: 'string' },
availableScopes: { type: 'object', additionalProperties: { type: 'string' } },
authorizationUrl: { type: 'string' },
},
required: ['authorizationUrl', 'availableScopes'],
};
const PasswordFlow: NodeType = {
properties: {
refreshUrl: { type: 'string' },
availableScopes: { type: 'object', additionalProperties: { type: 'string' } },
tokenUrl: { type: 'string' },
},
required: ['tokenUrl', 'availableScopes'],
};
const ClientCredentials: NodeType = {
properties: {
refreshUrl: { type: 'string' },
availableScopes: { type: 'object', additionalProperties: { type: 'string' } },
tokenUrl: { type: 'string' },
},
required: ['tokenUrl', 'availableScopes'],
};
const AuthorizationCode: NodeType = {
properties: {
refreshUrl: { type: 'string' },
authorizationUrl: { type: 'string' },
availableScopes: { type: 'object', additionalProperties: { type: 'string' } },
tokenUrl: { type: 'string' },
},
required: ['authorizationUrl', 'tokenUrl', 'availableScopes'],
};
const SecurityScheme: NodeType = {
properties: {
type: {
enum: [
'userPassword',
'apiKey',
'X509',
'symmetricEncryption',
'asymmetricEncryption',
'httpApiKey',
'http',
'oauth2',
'openIdConnect',
'plain',
'scramSha256',
'scramSha512',
'gssapi',
],
},
description: { type: 'string' },
name: { type: 'string' },
in: { type: 'string', enum: ['query', 'header', 'cookie', 'user', 'password'] },
scheme: { type: 'string' },
bearerFormat: { type: 'string' },
flows: 'SecuritySchemeFlows',
openIdConnectUrl: { type: 'string' },
scopes: { type: 'array', items: { type: 'string' } },
},
required(value) {
switch (value?.type) {
case 'apiKey':
return ['type', 'in'];
case 'httpApiKey':
return ['type', 'name', 'in'];
case 'http':
return ['type', 'scheme'];
case 'oauth2':
return ['type', 'flows'];
case 'openIdConnect':
return ['type', 'openIdConnectUrl'];
default:
return ['type'];
}
},
allowed(value) {
switch (value?.type) {
case 'apiKey':
return ['type', 'in', 'description'];
case 'httpApiKey':
return ['type', 'name', 'in', 'description'];
case 'http':
return ['type', 'scheme', 'bearerFormat', 'description'];
case 'oauth2':
return ['type', 'flows', 'description', 'scopes'];
case 'openIdConnect':
return ['type', 'openIdConnectUrl', 'description', 'scopes'];
default:
return ['type', 'description'];
}
},
extensionsPrefix: 'x-',
};
export const AsyncApi3Types: Record<string, NodeType> = {
// from asyncapi2
...AsyncApi2Bindings,
CorrelationId,
SecuritySchemeFlows,
ServerVariable,
Contact,
License,
MessageExample,
Tag,
Dependencies,
Schema,
Discriminator,
DiscriminatorMapping,
SchemaProperties,
ServerMap,
ExternalDocs,
Root,
Channel,
Parameter,
Info,
Server,
MessageTrait,
Operation,
OperationReply,
OperationReplyAddress,
Components,
ImplicitFlow,
PasswordFlow,
ClientCredentials,
AuthorizationCode,
SecurityScheme,
Message,
OperationTrait,
ServerVariablesMap: mapOf('ServerVariable'),
NamedTags: mapOf('Tag'),
NamedExternalDocs: mapOf('ExternalDocs'),
NamedChannels: mapOf('Channel'),
ParametersMap: mapOf('Parameter'),
NamedOperations: mapOf('Operation'),
NamedOperationReplies: mapOf('OperationReply'),
NamedOperationRelyAddresses: mapOf('OperationReplyAddress'),
NamedSchemas: mapOf('Schema'),
NamedMessages: mapOf('Message'),
NamedMessageTraits: mapOf('MessageTrait'),
NamedOperationTraits: mapOf('OperationTrait'),
NamedParameters: mapOf('Parameter'),
NamedSecuritySchemes: mapOf('SecurityScheme'),
NamedCorrelationIds: mapOf('CorrelationId'),
ServerList: listOf('Server'),
SecuritySchemeList: listOf('SecurityScheme'),
MessageList: listOf('Message'),
OperationTraitList: listOf('OperationTrait'),
MessageTraitList: listOf('MessageTrait'),
MessageExampleList: listOf('MessageExample'),
TagList: listOf('Tag'),
};

View File

@@ -92,8 +92,20 @@ const builtInAsync2Rules = [
'no-channel-trailing-slash',
] as const;
const builtInAsync3Rules = [
'spec',
'info-contact',
'operation-operationId',
'tag-description',
'tags-alphabetical',
'channels-kebab-case',
'no-channel-trailing-slash',
] as const;
export type BuiltInAsync2RuleId = typeof builtInAsync2Rules[number];
export type BuiltInAsync3RuleId = typeof builtInAsync3Rules[number];
const builtInArazzoRules = ['spec'] as const;
export type BuiltInArazzoRuleId = typeof builtInArazzoRules[number];
@@ -103,6 +115,7 @@ const builtInRules = [
...builtInOAS2Rules,
...builtInOAS3Rules,
...builtInAsync2Rules,
...builtInAsync3Rules,
...builtInArazzoRules,
] as const;

View File

@@ -0,0 +1,61 @@
export interface Async3Definition {
asyncapi: string;
servers?: Record<string, any>;
info: Async3Info;
channels?: Record<string, Channel>;
components?: Record<string, any>;
operations?: Record<string, any>;
defaultContentType?: string;
}
export interface Async3Info {
title: string;
version: string;
description?: string;
termsOfService?: string;
contact?: Async3Contact;
license?: Async3License;
tags?: Tag[];
externalDocs?: ExternalDoc;
}
export interface Async3Contact {
name?: string;
url?: string;
email?: string;
}
export interface Async3License {
name: string;
url?: string;
}
export interface Tag {
name: string;
description?: string;
externalDocs?: ExternalDoc;
}
export interface ExternalDoc {
url: string;
description?: string;
}
export interface Channel {
address?: string | null;
messages?: Record<string, any>;
title?: string;
summary?: string;
description?: string;
servers?: Record<string, any>[];
parameters?: Record<string, any>;
tags?: Record<string, any>;
externalDocs?: ExternalDocumentation;
bindings?: Record<string, any>;
}
export interface ExternalDocumentation {
url: string;
description?: string;
}

View File

@@ -50,6 +50,7 @@ import type {
Oas2SecurityScheme,
} from './typings/swagger';
import type { Async2Definition } from './typings/asyncapi';
import type { Async3Definition } from './typings/asyncapi3';
import type { ArazzoDefinition } from './typings/arazzo';
export type SkipFunctionContext = Pick<
@@ -213,6 +214,10 @@ type Async2FlatVisitor = {
Root?: VisitFunctionOrObject<Async2Definition>;
};
type Async3FlatVisitor = {
Root?: VisitFunctionOrObject<Async3Definition>;
};
type ArazzoFlatVisitor = {
Root?: VisitFunctionOrObject<ArazzoDefinition>;
};
@@ -249,6 +254,12 @@ type Async2NestedVisitor = {
: Async2FlatVisitor[T] & NestedVisitor<Async2NestedVisitor>;
};
type Async3NestedVisitor = {
[T in keyof Async3FlatVisitor]: Async3FlatVisitor[T] extends Function
? Async3FlatVisitor[T]
: Async3FlatVisitor[T] & NestedVisitor<Async3NestedVisitor>;
};
type ArazzoNestedVisitor = {
[T in keyof ArazzoFlatVisitor]: ArazzoFlatVisitor[T] extends Function
? ArazzoFlatVisitor[T]
@@ -267,6 +278,10 @@ export type Async2Visitor = BaseVisitor &
Async2NestedVisitor &
Record<string, VisitFunction<any> | NestedVisitObject<any, Async2NestedVisitor>>;
export type Async3Visitor = BaseVisitor &
Async3NestedVisitor &
Record<string, VisitFunction<any> | NestedVisitObject<any, Async3NestedVisitor>>;
export type ArazzoVisitor = BaseVisitor &
ArazzoNestedVisitor &
Record<string, VisitFunction<any> | NestedVisitObject<any, ArazzoNestedVisitor>>;
@@ -293,14 +308,17 @@ export type NormalizedOasVisitors<T extends BaseVisitor> = {
export type Oas3Rule = (options: Record<string, any>) => Oas3Visitor | Oas3Visitor[];
export type Oas2Rule = (options: Record<string, any>) => Oas2Visitor | Oas2Visitor[];
export type Async2Rule = (options: Record<string, any>) => Async2Visitor | Async2Visitor[];
export type Async3Rule = (options: Record<string, any>) => Async3Visitor | Async3Visitor[];
export type ArazzoRule = (options: Record<string, any>) => ArazzoVisitor | ArazzoVisitor[];
export type Oas3Preprocessor = (options: Record<string, any>) => Oas3Visitor;
export type Oas2Preprocessor = (options: Record<string, any>) => Oas2Visitor;
export type Async2Preprocessor = (options: Record<string, any>) => Async2Visitor;
export type Async3Preprocessor = (options: Record<string, any>) => Async3Visitor;
export type ArazzoPreprocessor = (options: Record<string, any>) => ArazzoVisitor;
export type Oas3Decorator = (options: Record<string, any>) => Oas3Visitor;
export type Oas2Decorator = (options: Record<string, any>) => Oas2Visitor;
export type Async2Decorator = (options: Record<string, any>) => Async2Visitor;
export type Async3Decorator = (options: Record<string, any>) => Async3Visitor;
export type ArazzoDecorator = (options: Record<string, any>) => ArazzoVisitor;
// alias for the latest version supported

View File

@@ -173,7 +173,7 @@ channels:
description: The Id of the company.
additionalProperties: false
smartylighting/streetlights/1/0/action/{streetlightId}/turn/on:
smartylighting/streetlights/1/0/action/{streetlightId}/turn/on/:
parameters:
streetlightId:
$ref: '#/components/parameters/streetlightId'

31
resources/asyncapi3.yaml Normal file
View File

@@ -0,0 +1,31 @@
asyncapi: 3.0.0
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user signups
channels:
userSignedup:
address: user/signedup/
messages:
UserSignedUp:
$ref: '#/components/messages/UserSignedUp'
operations:
sendUserSignedup:
action: send
channel:
$ref: '#/channels/userSignedup'
messages:
- $ref: '#/channels/userSignedup/messages/UserSignedUp'
components:
messages:
UserSignedUp:
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user