├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md └── workflows │ ├── cloc.yml │ ├── golangci-lint.yml │ ├── gorelease.yml │ └── test-unit.yml ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── dev_test.go ├── go.mod ├── go.sum ├── reflector ├── asyncapi-2.0.0 │ ├── example_test.go │ ├── reflect.go │ ├── reflect_test.go │ ├── sample.md │ └── sample.yaml ├── asyncapi-2.1.0 │ ├── example_test.go │ ├── reflect.go │ ├── reflect_test.go │ └── sample.yaml └── asyncapi-2.4.0 │ ├── example_test.go │ ├── reflect.go │ ├── reflect_test.go │ ├── sample-amqp.yaml │ └── sample-kafka.yaml ├── resources ├── fixtures │ ├── streetlights-2.0.0.json │ ├── streetlights-2.0.0.yml │ ├── streetlights-2.1.0-kafka.json │ └── streetlights-2.1.0-kafka.yml └── schema │ ├── amqp-channel-binding-object-0.1.0.json │ ├── amqp-message-binding-object-0.1.0.json │ ├── amqp-operation-binding-object-0.1.0.json │ ├── asyncapi-2.0.0.json │ ├── asyncapi-2.1.0-patch.json │ ├── asyncapi-2.1.0.json │ ├── asyncapi-2.4.0-fixed.json │ ├── asyncapi-2.4.0-gen-cfg.json │ ├── asyncapi-2.4.0-patch.json │ ├── asyncapi-2.4.0.json │ ├── asyncapi.json │ ├── bindings-resolver.json │ └── prepare_bindings.sh ├── spec-2.0.0 ├── doc.go ├── entities.go ├── entities_test.go └── yaml.go ├── spec-2.1.0 ├── doc.go ├── entities.go ├── entities_test.go └── yaml.go ├── spec-2.4.0 ├── doc.go ├── entities.go ├── helper.go └── yaml.go └── spec ├── doc.go ├── entities.go ├── entities_test.go └── yaml.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | If feasible/relevant, please provide a code snippet (inline or with Go playground) to reproduce the issue. 15 | 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Additional context** 21 | Add any other context about the problem here. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please use discussions https://github.com/swaggest/go-asyncapi/discussions/categories/ideas to share feature ideas. 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Any question about features or usage 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please use discussions https://github.com/swaggest/go-asyncapi/discussions/categories/q-a to make your question more discoverable by other folks. 11 | -------------------------------------------------------------------------------- /.github/workflows/cloc.yml: -------------------------------------------------------------------------------- 1 | # This script is provided by github.com/bool64/dev. 2 | name: cloc 3 | on: 4 | pull_request: 5 | 6 | # Cancel the workflow in progress in newer build is about to start. 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | cloc: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v3 17 | with: 18 | path: pr 19 | - name: Checkout base code 20 | uses: actions/checkout@v3 21 | with: 22 | ref: ${{ github.event.pull_request.base.sha }} 23 | path: base 24 | - name: Count lines of code 25 | id: loc 26 | run: | 27 | curl -sLO https://github.com/vearutop/sccdiff/releases/download/v1.0.3/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz 28 | sccdiff_hash=$(git hash-object ./sccdiff) 29 | [ "$sccdiff_hash" == "ae8a07b687bd3dba60861584efe724351aa7ff63" ] || (echo "::error::unexpected hash for sccdiff, possible tampering: $sccdiff_hash" && exit 1) 30 | OUTPUT=$(cd pr && ../sccdiff -basedir ../base) 31 | echo "${OUTPUT}" 32 | echo "diff<> $GITHUB_OUTPUT && echo "$OUTPUT" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT 33 | 34 | - name: Comment lines of code 35 | continue-on-error: true 36 | uses: marocchino/sticky-pull-request-comment@v2 37 | with: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | header: LOC 40 | message: | 41 | ### Lines Of Code 42 | 43 | ${{ steps.loc.outputs.diff }} 44 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | # This script is provided by github.com/bool64/dev. 2 | name: lint 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | branches: 8 | - master 9 | - main 10 | pull_request: 11 | 12 | # Cancel the workflow in progress in newer build is about to start. 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | golangci: 19 | name: golangci-lint 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/setup-go@v3 23 | with: 24 | go-version: 1.23.x 25 | - uses: actions/checkout@v2 26 | - name: golangci-lint 27 | uses: golangci/golangci-lint-action@v6.1.0 28 | with: 29 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 30 | version: v1.61.0 31 | 32 | # Optional: working directory, useful for monorepos 33 | # working-directory: somedir 34 | 35 | # Optional: golangci-lint command line arguments. 36 | # args: --issues-exit-code=0 37 | 38 | # Optional: show only new issues if it's a pull request. The default value is `false`. 39 | # only-new-issues: true 40 | 41 | # Optional: if set to true then the action will use pre-installed Go. 42 | # skip-go-installation: true 43 | 44 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 45 | # skip-pkg-cache: true 46 | 47 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 48 | # skip-build-cache: true -------------------------------------------------------------------------------- /.github/workflows/gorelease.yml: -------------------------------------------------------------------------------- 1 | # This script is provided by github.com/bool64/dev. 2 | name: gorelease 3 | on: 4 | pull_request: 5 | 6 | # Cancel the workflow in progress in newer build is about to start. 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 9 | cancel-in-progress: true 10 | 11 | env: 12 | GO_VERSION: 1.23.x 13 | jobs: 14 | gorelease: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Install Go stable 18 | if: env.GO_VERSION != 'tip' 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: ${{ env.GO_VERSION }} 22 | - name: Install Go tip 23 | if: env.GO_VERSION == 'tip' 24 | run: | 25 | curl -sL https://storage.googleapis.com/go-build-snap/go/linux-amd64/$(git ls-remote https://github.com/golang/go.git HEAD | awk '{print $1;}').tar.gz -o gotip.tar.gz 26 | ls -lah gotip.tar.gz 27 | mkdir -p ~/sdk/gotip 28 | tar -C ~/sdk/gotip -xzf gotip.tar.gz 29 | ~/sdk/gotip/bin/go version 30 | echo "PATH=$HOME/go/bin:$HOME/sdk/gotip/bin/:$PATH" >> $GITHUB_ENV 31 | - name: Checkout code 32 | uses: actions/checkout@v3 33 | - name: Gorelease cache 34 | uses: actions/cache@v3 35 | with: 36 | path: | 37 | ~/go/bin/gorelease 38 | key: ${{ runner.os }}-gorelease-generic 39 | - name: Gorelease 40 | id: gorelease 41 | run: | 42 | test -e ~/go/bin/gorelease || go install golang.org/x/exp/cmd/gorelease@latest 43 | OUTPUT=$(gorelease 2>&1 || exit 0) 44 | echo "${OUTPUT}" 45 | echo "report<> $GITHUB_OUTPUT && echo "$OUTPUT" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT 46 | - name: Comment report 47 | continue-on-error: true 48 | uses: marocchino/sticky-pull-request-comment@v2 49 | with: 50 | header: gorelease 51 | message: | 52 | ### Go API Changes 53 | 54 |
55 |             ${{ steps.gorelease.outputs.report }}
56 |             
-------------------------------------------------------------------------------- /.github/workflows/test-unit.yml: -------------------------------------------------------------------------------- 1 | # This script is provided by github.com/bool64/dev. 2 | name: test-unit 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | 10 | # Cancel the workflow in progress in newer build is about to start. 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 13 | cancel-in-progress: true 14 | 15 | env: 16 | GO111MODULE: "on" 17 | RUN_BASE_COVERAGE: "on" # Runs test for PR base in case base test coverage is missing. 18 | COV_GO_VERSION: 1.22.x # Version of Go to collect coverage 19 | TARGET_DELTA_COV: 90 # Target coverage of changed lines, in percents 20 | jobs: 21 | test: 22 | strategy: 23 | matrix: 24 | go-version: [ 1.16.x, 1.22.x, 1.23.x ] 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Install Go stable 28 | if: matrix.go-version != 'tip' 29 | uses: actions/setup-go@v4 30 | with: 31 | go-version: ${{ matrix.go-version }} 32 | 33 | - name: Install Go tip 34 | if: matrix.go-version == 'tip' 35 | run: | 36 | curl -sL https://storage.googleapis.com/go-build-snap/go/linux-amd64/$(git ls-remote https://github.com/golang/go.git HEAD | awk '{print $1;}').tar.gz -o gotip.tar.gz 37 | ls -lah gotip.tar.gz 38 | mkdir -p ~/sdk/gotip 39 | tar -C ~/sdk/gotip -xzf gotip.tar.gz 40 | ~/sdk/gotip/bin/go version 41 | echo "PATH=$HOME/go/bin:$HOME/sdk/gotip/bin/:$PATH" >> $GITHUB_ENV 42 | 43 | - name: Checkout code 44 | uses: actions/checkout@v3 45 | 46 | - name: Go cache 47 | uses: actions/cache@v3 48 | with: 49 | # In order: 50 | # * Module download cache 51 | # * Build cache (Linux) 52 | path: | 53 | ~/go/pkg/mod 54 | ~/.cache/go-build 55 | key: ${{ runner.os }}-go-cache-${{ hashFiles('**/go.sum') }} 56 | restore-keys: | 57 | ${{ runner.os }}-go-cache 58 | 59 | - name: Restore base test coverage 60 | id: base-coverage 61 | if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != '' 62 | uses: actions/cache@v2 63 | with: 64 | path: | 65 | unit-base.txt 66 | # Use base sha for PR or new commit hash for master/main push in test result key. 67 | key: ${{ runner.os }}-unit-test-coverage-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }} 68 | 69 | - name: Run test for base code 70 | if: matrix.go-version == env.COV_GO_VERSION && env.RUN_BASE_COVERAGE == 'on' && steps.base-coverage.outputs.cache-hit != 'true' && github.event.pull_request.base.sha != '' 71 | run: | 72 | git fetch origin master ${{ github.event.pull_request.base.sha }} 73 | HEAD=$(git rev-parse HEAD) 74 | git reset --hard ${{ github.event.pull_request.base.sha }} 75 | (make test-unit && go tool cover -func=./unit.coverprofile > unit-base.txt) || echo "No test-unit in base" 76 | git reset --hard $HEAD 77 | 78 | - name: Test 79 | id: test 80 | run: | 81 | make test-unit 82 | go tool cover -func=./unit.coverprofile > unit.txt 83 | TOTAL=$(grep 'total:' unit.txt) 84 | echo "${TOTAL}" 85 | echo "total=$TOTAL" >> $GITHUB_OUTPUT 86 | 87 | - name: Annotate missing test coverage 88 | id: annotate 89 | if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != '' 90 | run: | 91 | curl -sLO https://github.com/vearutop/gocovdiff/releases/download/v1.4.2/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz && rm linux_amd64.tar.gz 92 | gocovdiff_hash=$(git hash-object ./gocovdiff) 93 | [ "$gocovdiff_hash" == "c37862c73a677e5a9c069470287823ab5bbf0244" ] || (echo "::error::unexpected hash for gocovdiff, possible tampering: $gocovdiff_hash" && exit 1) 94 | git fetch origin master ${{ github.event.pull_request.base.sha }} 95 | REP=$(./gocovdiff -mod github.com/$GITHUB_REPOSITORY -cov unit.coverprofile -gha-annotations gha-unit.txt -delta-cov-file delta-cov-unit.txt -target-delta-cov ${TARGET_DELTA_COV}) 96 | echo "${REP}" 97 | cat gha-unit.txt 98 | DIFF=$(test -e unit-base.txt && ./gocovdiff -mod github.com/$GITHUB_REPOSITORY -func-cov unit.txt -func-base-cov unit-base.txt || echo "Missing base coverage file") 99 | TOTAL=$(cat delta-cov-unit.txt) 100 | echo "rep<> $GITHUB_OUTPUT && echo "$REP" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT 101 | echo "diff<> $GITHUB_OUTPUT && echo "$DIFF" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT 102 | echo "total<> $GITHUB_OUTPUT && echo "$TOTAL" >> $GITHUB_OUTPUT && echo "EOF" >> $GITHUB_OUTPUT 103 | 104 | - name: Comment test coverage 105 | continue-on-error: true 106 | if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != '' 107 | uses: marocchino/sticky-pull-request-comment@v2 108 | with: 109 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 110 | header: unit-test 111 | message: | 112 | ### Unit Test Coverage 113 | ${{ steps.test.outputs.total }} 114 | ${{ steps.annotate.outputs.total }} 115 |
Coverage of changed lines 116 | 117 | ${{ steps.annotate.outputs.rep }} 118 | 119 |
120 | 121 |
Coverage diff with base branch 122 | 123 | ${{ steps.annotate.outputs.diff }} 124 | 125 |
126 | 127 | - name: Store base coverage 128 | if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }} 129 | run: cp unit.txt unit-base.txt 130 | 131 | - name: Upload code coverage 132 | if: matrix.go-version == env.COV_GO_VERSION 133 | uses: codecov/codecov-action@v1 134 | with: 135 | file: ./unit.coverprofile 136 | flags: unittests 137 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /*.coverprofile 3 | /.vscode 4 | /bench-*.txt 5 | /vendor 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "resources/schema/bindings"] 2 | path = resources/schema/bindings 3 | url = https://github.com/asyncapi/bindings.git 4 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml 2 | run: 3 | tests: true 4 | 5 | linters-settings: 6 | errcheck: 7 | check-type-assertions: true 8 | check-blank: true 9 | gocyclo: 10 | min-complexity: 20 11 | dupl: 12 | threshold: 100 13 | misspell: 14 | locale: US 15 | unused: 16 | check-exported: false 17 | unparam: 18 | check-exported: true 19 | 20 | linters: 21 | enable-all: true 22 | disable: 23 | - gci 24 | - funlen 25 | - cyclop 26 | - err113 27 | - lll 28 | - gochecknoglobals 29 | - gomnd 30 | - wrapcheck 31 | - paralleltest 32 | - forbidigo 33 | - forcetypeassert 34 | - varnamelen 35 | - tagliatelle 36 | - errname 37 | - ireturn 38 | - exhaustruct 39 | - nonamedreturns 40 | - testableexamples 41 | - dupword 42 | - depguard 43 | - tagalign 44 | - execinquery 45 | - mnd 46 | - testifylint 47 | 48 | issues: 49 | exclude-use-default: false 50 | exclude-rules: 51 | - linters: 52 | - gomnd 53 | - mnd 54 | - goconst 55 | - goerr113 56 | - noctx 57 | - funlen 58 | - dupl 59 | - unused 60 | - unparam 61 | path: "_test.go" 62 | - linters: 63 | - errcheck # Error checking omitted for brevity. 64 | - gosec 65 | path: "example_" 66 | - linters: 67 | - revive 68 | text: "unused-parameter: parameter" 69 | 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Viacheslav Poturaev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #GOLANGCI_LINT_VERSION := "v1.61.0" # Optional configuration to pinpoint golangci-lint version. 2 | 3 | # The head of Makefile determines location of dev-go to include standard targets. 4 | GO ?= go 5 | export GO111MODULE = on 6 | 7 | ifneq "$(GOFLAGS)" "" 8 | $(info GOFLAGS: ${GOFLAGS}) 9 | endif 10 | 11 | ifneq "$(wildcard ./vendor )" "" 12 | $(info Using vendor) 13 | modVendor = -mod=vendor 14 | ifeq (,$(findstring -mod,$(GOFLAGS))) 15 | export GOFLAGS := ${GOFLAGS} ${modVendor} 16 | endif 17 | ifneq "$(wildcard ./vendor/github.com/bool64/dev)" "" 18 | DEVGO_PATH := ./vendor/github.com/bool64/dev 19 | endif 20 | endif 21 | 22 | ifeq ($(DEVGO_PATH),) 23 | DEVGO_PATH := $(shell GO111MODULE=on $(GO) list ${modVendor} -f '{{.Dir}}' -m github.com/bool64/dev) 24 | ifeq ($(DEVGO_PATH),) 25 | $(info Module github.com/bool64/dev not found, downloading.) 26 | DEVGO_PATH := $(shell export GO111MODULE=on && $(GO) get github.com/bool64/dev && $(GO) list -f '{{.Dir}}' -m github.com/bool64/dev) 27 | endif 28 | endif 29 | 30 | -include $(DEVGO_PATH)/makefiles/main.mk 31 | -include $(DEVGO_PATH)/makefiles/lint.mk 32 | -include $(DEVGO_PATH)/makefiles/test-unit.mk 33 | -include $(DEVGO_PATH)/makefiles/bench.mk 34 | -include $(DEVGO_PATH)/makefiles/reset-ci.mk 35 | 36 | # Add your custom targets here. 37 | 38 | ## Run tests 39 | test: test-unit 40 | 41 | JSON_CLI_VERSION := v1.11.0 42 | 43 | ## Generate bindings for v2.4.0 spec. 44 | gen-2.4.0: 45 | @test -s $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) || (curl -sSfL https://github.com/swaggest/json-cli/releases/download/$(JSON_CLI_VERSION)/json-cli -o $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) && chmod +x $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION)) 46 | cd resources/schema/ && ./prepare_bindings.sh && $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) gen-go asyncapi-2.4.0-fixed.json --output ../../spec-2.4.0/entities.go --fluent-setters --package-name spec --root-name AsyncAPI --config ./asyncapi-2.4.0-gen-cfg.json --schema-resolver ./bindings-resolver.json 47 | make fix-lint 48 | 49 | 50 | ## Generate bindings for v2.1.0 spec. 51 | gen-2.1.0: 52 | @test -s $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) || (curl -sSfL https://github.com/swaggest/json-cli/releases/download/$(JSON_CLI_VERSION)/json-cli -o $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) && chmod +x $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION)) 53 | cd resources/schema/ && $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) gen-go asyncapi-2.1.0.json --patches asyncapi-2.1.0-patch.json --output ../../spec-2.1.0/entities.go --validate-required --fluent-setters --package-name spec --root-name AsyncAPI 54 | make fix-lint 55 | 56 | ## Generate bindings for v2.0.0 spec. 57 | gen-2.0.0: 58 | @test -s $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) || (curl -sSfL https://github.com/swaggest/json-cli/releases/download/$(JSON_CLI_VERSION)/json-cli -o $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) && chmod +x $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION)) 59 | cd resources/schema/ && $(GOPATH)/bin/json-cli-$(JSON_CLI_VERSION) gen-go asyncapi-2.0.0.json --output ../../spec-2.0.0/entities.go --validate-required --fluent-setters --package-name spec --root-name AsyncAPI 60 | make fix-lint 61 | 62 | ## Generate bindings for v1.2.0 spec. 63 | gen-1.2.0: 64 | json-cli gen-go resources/schema/asyncapi.json --output ./spec/entities.go --fluent-setters --package-name spec --root-name AsyncAPI \ 65 | --renames AsyncAPIAsyncapi100:Asyncapi100 AsyncAPIAsyncapi110:Asyncapi110 AsyncAPIAsyncapi120:Asyncapi120 66 | make fix-lint 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AsyncAPI Generator for Go 2 | 3 | [![Build Status](https://github.com/swaggest/go-asyncapi/workflows/test-unit/badge.svg)](https://github.com/swaggest/go-asyncapi/actions?query=branch%3Amaster+workflow%3Atest-unit) 4 | [![Coverage Status](https://codecov.io/gh/swaggest/go-asyncapi/branch/master/graph/badge.svg)](https://codecov.io/gh/swaggest/go-asyncapi) 5 | [![GoDoc](https://godoc.org/github.com/swaggest/go-asyncapi?status.svg)](https://godoc.org/github.com/swaggest/go-asyncapi) 6 | ![Code lines](https://sloc.xyz/github/swaggest/go-asyncapi/?category=code) 7 | ![Comments](https://sloc.xyz/github/swaggest/go-asyncapi/?category=comments) 8 | 9 | This library helps to create [AsyncAPI](https://www.asyncapi.com/) spec from your Go message structures. 10 | 11 | Supported AsyncAPI versions: 12 | * `v2.4.0` 13 | * `v2.1.0` 14 | * `v2.0.0` 15 | * `v1.2.0` 16 | 17 | ## Example 18 | 19 | ```go 20 | package asyncapi_test 21 | 22 | import ( 23 | "fmt" 24 | "os" 25 | "time" 26 | 27 | "github.com/swaggest/go-asyncapi/reflector/asyncapi-2.4.0" 28 | "github.com/swaggest/go-asyncapi/spec-2.4.0" 29 | ) 30 | 31 | func main() { 32 | type SubItem struct { 33 | Key string `json:"key" description:"Item key"` 34 | Values []int64 `json:"values" uniqueItems:"true" description:"List of item values"` 35 | } 36 | 37 | type MyMessage struct { 38 | Name string `path:"name" description:"Name"` 39 | CreatedAt time.Time `json:"createdAt" description:"Creation time"` 40 | Items []SubItem `json:"items" description:"List of items"` 41 | } 42 | 43 | type MyAnotherMessage struct { 44 | TraceID string `header:"X-Trace-ID" description:"Tracing header" required:"true"` 45 | Item SubItem `json:"item" description:"Some item"` 46 | } 47 | 48 | asyncAPI := spec.AsyncAPI{} 49 | asyncAPI.Info.Version = "1.2.3" 50 | asyncAPI.Info.Title = "My Lovely Messaging API" 51 | 52 | asyncAPI.AddServer("live", spec.Server{ 53 | URL: "api.{country}.lovely.com:5672", 54 | Description: "Production instance.", 55 | ProtocolVersion: "0.9.1", 56 | Protocol: "amqp", 57 | Variables: map[string]spec.ServerVariable{ 58 | "country": { 59 | Enum: []string{"RU", "US", "DE", "FR"}, 60 | Default: "US", 61 | Description: "Country code.", 62 | }, 63 | }, 64 | }) 65 | 66 | reflector := asyncapi.Reflector{} 67 | reflector.Schema = &asyncAPI 68 | 69 | mustNotFail := func(err error) { 70 | if err != nil { 71 | panic(err.Error()) 72 | } 73 | } 74 | 75 | mustNotFail(reflector.AddChannel(asyncapi.ChannelInfo{ 76 | Name: "one.{name}.two", 77 | BaseChannelItem: &spec.ChannelItem{ 78 | Bindings: &spec.ChannelBindingsObject{ 79 | Amqp: &spec.AmqpChannel{ 80 | Is: spec.AmqpChannelIsRoutingKey, 81 | Exchange: &spec.AmqpChannelExchange{ 82 | Name: "some-exchange", 83 | }, 84 | }, 85 | }, 86 | }, 87 | Publish: &asyncapi.MessageSample{ 88 | MessageEntity: spec.MessageEntity{ 89 | Description: "This is a sample schema.", 90 | Summary: "Sample publisher", 91 | }, 92 | MessageSample: new(MyMessage), 93 | }, 94 | })) 95 | 96 | mustNotFail(reflector.AddChannel(asyncapi.ChannelInfo{ 97 | Name: "another.one", 98 | Subscribe: &asyncapi.MessageSample{ 99 | MessageEntity: spec.MessageEntity{ 100 | Description: "This is another sample schema.", 101 | Summary: "Sample consumer", 102 | }, 103 | MessageSample: new(MyAnotherMessage), 104 | }, 105 | })) 106 | 107 | yaml, err := reflector.Schema.MarshalYAML() 108 | mustNotFail(err) 109 | 110 | fmt.Println(string(yaml)) 111 | mustNotFail(os.WriteFile("sample.yaml", yaml, 0o600)) 112 | // output: 113 | // asyncapi: 2.4.0 114 | // info: 115 | // title: My Lovely Messaging API 116 | // version: 1.2.3 117 | // servers: 118 | // live: 119 | // url: api.{country}.lovely.com:5672 120 | // description: Production instance. 121 | // protocol: amqp 122 | // protocolVersion: 0.9.1 123 | // variables: 124 | // country: 125 | // enum: 126 | // - RU 127 | // - US 128 | // - DE 129 | // - FR 130 | // default: US 131 | // description: Country code. 132 | // channels: 133 | // another.one: 134 | // subscribe: 135 | // message: 136 | // $ref: '#/components/messages/Asyncapi240TestMyAnotherMessage' 137 | // one.{name}.two: 138 | // parameters: 139 | // name: 140 | // schema: 141 | // description: Name 142 | // type: string 143 | // publish: 144 | // message: 145 | // $ref: '#/components/messages/Asyncapi240TestMyMessage' 146 | // bindings: 147 | // amqp: 148 | // bindingVersion: 0.2.0 149 | // is: routingKey 150 | // exchange: 151 | // name: some-exchange 152 | // components: 153 | // schemas: 154 | // Asyncapi240TestMyAnotherMessage: 155 | // properties: 156 | // item: 157 | // $ref: '#/components/schemas/Asyncapi240TestSubItem' 158 | // description: Some item 159 | // type: object 160 | // Asyncapi240TestMyMessage: 161 | // properties: 162 | // createdAt: 163 | // description: Creation time 164 | // format: date-time 165 | // type: string 166 | // items: 167 | // description: List of items 168 | // items: 169 | // $ref: '#/components/schemas/Asyncapi240TestSubItem' 170 | // type: 171 | // - array 172 | // - "null" 173 | // type: object 174 | // Asyncapi240TestSubItem: 175 | // properties: 176 | // key: 177 | // description: Item key 178 | // type: string 179 | // values: 180 | // description: List of item values 181 | // items: 182 | // type: integer 183 | // type: 184 | // - array 185 | // - "null" 186 | // uniqueItems: true 187 | // type: object 188 | // messages: 189 | // Asyncapi240TestMyAnotherMessage: 190 | // headers: 191 | // properties: 192 | // X-Trace-ID: 193 | // description: Tracing header 194 | // type: string 195 | // required: 196 | // - X-Trace-ID 197 | // type: object 198 | // payload: 199 | // $ref: '#/components/schemas/Asyncapi240TestMyAnotherMessage' 200 | // summary: Sample consumer 201 | // description: This is another sample schema. 202 | // Asyncapi240TestMyMessage: 203 | // payload: 204 | // $ref: '#/components/schemas/Asyncapi240TestMyMessage' 205 | // summary: Sample publisher 206 | // description: This is a sample schema. 207 | } 208 | ``` 209 | -------------------------------------------------------------------------------- /dev_test.go: -------------------------------------------------------------------------------- 1 | package mypackage_test 2 | 3 | import _ "github.com/bool64/dev" // Include CI/Dev scripts to project. 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/swaggest/go-asyncapi 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/bool64/dev v0.2.36 7 | github.com/stretchr/testify v1.8.2 8 | github.com/swaggest/assertjson v1.9.0 9 | github.com/swaggest/jsonschema-go v0.3.72 10 | gopkg.in/yaml.v2 v2.4.0 11 | ) 12 | 13 | require ( 14 | github.com/bool64/shared v0.1.5 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/fsnotify/fsnotify v1.4.9 // indirect 17 | github.com/iancoleman/orderedmap v0.3.0 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | github.com/sergi/go-diff v1.3.1 // indirect 20 | github.com/swaggest/refl v1.3.0 // indirect 21 | github.com/yudai/gojsondiff v1.0.0 // indirect 22 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect 23 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 24 | gopkg.in/yaml.v3 v3.0.1 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bool64/dev v0.2.17/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 2 | github.com/bool64/dev v0.2.29/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 3 | github.com/bool64/dev v0.2.31/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 4 | github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 5 | github.com/bool64/dev v0.2.36 h1:yU3bbOTujoxhWnt8ig8t94PVmZXIkCaRj9C57OtqJBY= 6 | github.com/bool64/dev v0.2.36/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= 7 | github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= 8 | github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 13 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 14 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 15 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 16 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 17 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 18 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 19 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 20 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 21 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 22 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 23 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 24 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 25 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 26 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 27 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 28 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 29 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 30 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 31 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 32 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 33 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 34 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 35 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 36 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 37 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 38 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 39 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 40 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 41 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 42 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 43 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 44 | github.com/onsi/ginkgo v1.15.2 h1:l77YT15o814C2qVL47NOyjV/6RbaP7kKdrvZnxQ3Org= 45 | github.com/onsi/ginkgo v1.15.2/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o= 46 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 47 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 48 | github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= 49 | github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= 50 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 51 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 52 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 53 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 54 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 55 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 56 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 57 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 58 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 59 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 60 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 61 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 62 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 63 | github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= 64 | github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= 65 | github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNyNzvnVHv4= 66 | github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4= 67 | github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= 68 | github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= 69 | github.com/yosuke-furukawa/json5 v0.1.2-0.20201207051438-cf7bb3f354ff/go.mod h1:sw49aWDqNdRJ6DYUtIQiaA3xyj2IL9tjeNYmX2ixwcU= 70 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 71 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 72 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 73 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 74 | github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= 75 | github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= 76 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 77 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 78 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 79 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 80 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 81 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 82 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 83 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 84 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 85 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 86 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 87 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 88 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 89 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 90 | golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 91 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 92 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 93 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 94 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 95 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 96 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 97 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 98 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 99 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 100 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 101 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 102 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 103 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 105 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 106 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 107 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 108 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 109 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 110 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 111 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 112 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 113 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 114 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 115 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 116 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 117 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 118 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 119 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 120 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 121 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 122 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 123 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 124 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 125 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 126 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 127 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 128 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 129 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 130 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 131 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 132 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 133 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 134 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 135 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 136 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 137 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 138 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 139 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 140 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 141 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 142 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 143 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 144 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 145 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 146 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 147 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 148 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 149 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 150 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 151 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 152 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 153 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 154 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 155 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 156 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 157 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.0.0/example_test.go: -------------------------------------------------------------------------------- 1 | package asyncapi_test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/swaggest/go-asyncapi/reflector/asyncapi-2.0.0" 9 | "github.com/swaggest/go-asyncapi/spec-2.0.0" 10 | ) 11 | 12 | func ExampleReflector_AddChannel() { 13 | type SubItem struct { 14 | Key string `json:"key" description:"Item key"` 15 | Values []int64 `json:"values" uniqueItems:"true" description:"List of item values"` 16 | } 17 | 18 | type MyMessage struct { 19 | Name string `path:"name" description:"Name"` 20 | CreatedAt time.Time `json:"createdAt" description:"Creation time"` 21 | Items []SubItem `json:"items" description:"List of items"` 22 | } 23 | 24 | type MyAnotherMessage struct { 25 | TraceID string `header:"X-Trace-ID" description:"Tracing header" required:"true"` 26 | Item SubItem `json:"item" description:"Some item"` 27 | } 28 | 29 | reflector := asyncapi.Reflector{ 30 | Schema: &spec.AsyncAPI{ 31 | Servers: map[string]spec.Server{ 32 | "live": { 33 | URL: "api.{country}.lovely.com:5672", 34 | Description: "Production instance.", 35 | ProtocolVersion: "0.9.1", 36 | Protocol: "amqp", 37 | Variables: map[string]spec.ServerVariable{ 38 | "country": { 39 | Enum: []string{"RU", "US", "DE", "FR"}, 40 | Default: "US", 41 | Description: "Country code.", 42 | }, 43 | }, 44 | }, 45 | }, 46 | Info: spec.Info{ 47 | Version: "1.2.3", // required 48 | Title: "My Lovely Messaging API", 49 | }, 50 | }, 51 | } 52 | mustNotFail := func(err error) { 53 | if err != nil { 54 | panic(err.Error()) 55 | } 56 | } 57 | 58 | mustNotFail(reflector.AddChannel(asyncapi.ChannelInfo{ 59 | Name: "one.{name}.two", 60 | BaseChannelItem: &spec.ChannelItem{ 61 | Bindings: &spec.ChannelBindingsObject{ 62 | Amqp: &spec.AMQP091ChannelBindingObject{ 63 | Is: spec.AMQP091ChannelBindingObjectIsRoutingKey, 64 | Exchange: &spec.Exchange{ 65 | Name: "some-exchange", 66 | }, 67 | }, 68 | }, 69 | }, 70 | Publish: &asyncapi.MessageSample{ 71 | MessageEntity: spec.MessageEntity{ 72 | Description: "This is a sample schema.", 73 | Summary: "Sample publisher", 74 | }, 75 | MessageSample: new(MyMessage), 76 | }, 77 | })) 78 | 79 | mustNotFail(reflector.AddChannel(asyncapi.ChannelInfo{ 80 | Name: "another.one", 81 | Subscribe: &asyncapi.MessageSample{ 82 | MessageEntity: spec.MessageEntity{ 83 | Description: "This is another sample schema.", 84 | Summary: "Sample consumer", 85 | }, 86 | MessageSample: new(MyAnotherMessage), 87 | }, 88 | })) 89 | 90 | yaml, err := reflector.Schema.MarshalYAML() 91 | mustNotFail(err) 92 | 93 | fmt.Println(string(yaml)) 94 | mustNotFail(os.WriteFile("sample.yaml", yaml, 0o600)) 95 | // output: 96 | // asyncapi: 2.0.0 97 | // info: 98 | // title: My Lovely Messaging API 99 | // version: 1.2.3 100 | // servers: 101 | // live: 102 | // url: api.{country}.lovely.com:5672 103 | // description: Production instance. 104 | // protocol: amqp 105 | // protocolVersion: 0.9.1 106 | // variables: 107 | // country: 108 | // enum: 109 | // - RU 110 | // - US 111 | // - DE 112 | // - FR 113 | // default: US 114 | // description: Country code. 115 | // channels: 116 | // another.one: 117 | // subscribe: 118 | // message: 119 | // $ref: '#/components/messages/Asyncapi200TestMyAnotherMessage' 120 | // one.{name}.two: 121 | // parameters: 122 | // name: 123 | // schema: 124 | // description: Name 125 | // type: string 126 | // publish: 127 | // message: 128 | // $ref: '#/components/messages/Asyncapi200TestMyMessage' 129 | // bindings: 130 | // amqp: 131 | // is: routingKey 132 | // exchange: 133 | // name: some-exchange 134 | // components: 135 | // schemas: 136 | // Asyncapi200TestMyAnotherMessage: 137 | // properties: 138 | // item: 139 | // $ref: '#/components/schemas/Asyncapi200TestSubItem' 140 | // description: Some item 141 | // type: object 142 | // Asyncapi200TestMyMessage: 143 | // properties: 144 | // createdAt: 145 | // description: Creation time 146 | // format: date-time 147 | // type: string 148 | // items: 149 | // description: List of items 150 | // items: 151 | // $ref: '#/components/schemas/Asyncapi200TestSubItem' 152 | // type: 153 | // - array 154 | // - "null" 155 | // type: object 156 | // Asyncapi200TestSubItem: 157 | // properties: 158 | // key: 159 | // description: Item key 160 | // type: string 161 | // values: 162 | // description: List of item values 163 | // items: 164 | // type: integer 165 | // type: 166 | // - array 167 | // - "null" 168 | // uniqueItems: true 169 | // type: object 170 | // messages: 171 | // Asyncapi200TestMyAnotherMessage: 172 | // headers: 173 | // properties: 174 | // X-Trace-ID: 175 | // description: Tracing header 176 | // type: string 177 | // required: 178 | // - X-Trace-ID 179 | // type: object 180 | // payload: 181 | // $ref: '#/components/schemas/Asyncapi200TestMyAnotherMessage' 182 | // summary: Sample consumer 183 | // description: This is another sample schema. 184 | // Asyncapi200TestMyMessage: 185 | // payload: 186 | // $ref: '#/components/schemas/Asyncapi200TestMyMessage' 187 | // summary: Sample publisher 188 | // description: This is a sample schema. 189 | } 190 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.0.0/reflect.go: -------------------------------------------------------------------------------- 1 | // Package asyncapi provides schema reflector. 2 | package asyncapi 3 | 4 | import ( 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "strings" 9 | 10 | "github.com/swaggest/go-asyncapi/spec-2.0.0" 11 | "github.com/swaggest/jsonschema-go" 12 | ) 13 | 14 | // Reflector generates AsyncAPI definitions from provided message samples. 15 | type Reflector struct { 16 | jsonschema.Reflector 17 | Schema *spec.AsyncAPI 18 | } 19 | 20 | // DataEns ensures AsyncAPI Schema. 21 | // 22 | // Deprecated: use SchemaEns(). 23 | func (r *Reflector) DataEns() *spec.AsyncAPI { 24 | return r.SchemaEns() 25 | } 26 | 27 | // SchemaEns ensures AsyncAPI Schema. 28 | func (r *Reflector) SchemaEns() *spec.AsyncAPI { 29 | if r.Schema == nil { 30 | r.Schema = &spec.AsyncAPI{} 31 | } 32 | 33 | return r.Schema 34 | } 35 | 36 | // MessageSample is a structure that keeps general info and message sample (optional). 37 | type MessageSample struct { 38 | // pkg.Message holds general message info. 39 | spec.MessageEntity 40 | 41 | // MessageSample holds a sample of message to be converted to JSON Schema, e.g. `new(MyMessage)`. 42 | MessageSample interface{} 43 | } 44 | 45 | // ChannelInfo keeps user-defined information about channel. 46 | type ChannelInfo struct { 47 | Name string // event.{streetlightId}.lighting.measured 48 | Publish *MessageSample 49 | Subscribe *MessageSample 50 | BaseChannelItem *spec.ChannelItem // Optional, if set is used as a base to fill with Message data 51 | } 52 | 53 | // AddChannel adds user-defined channel to AsyncAPI definition. 54 | func (r *Reflector) AddChannel(info ChannelInfo) error { 55 | if info.Name == "" { 56 | return errors.New("name is required") 57 | } 58 | 59 | var ( 60 | channelItem = spec.ChannelItem{} 61 | err error 62 | ) 63 | 64 | if info.BaseChannelItem != nil { 65 | channelItem = *info.BaseChannelItem 66 | } 67 | 68 | if info.Publish != nil { 69 | channelItem.Publish, err = r.makeOperation(&channelItem, info.Publish) 70 | if err != nil { 71 | return fmt.Errorf("failed process publish operation for channel %s: %w", info.Name, err) 72 | } 73 | } 74 | 75 | if info.Subscribe != nil { 76 | channelItem.Subscribe, err = r.makeOperation(&channelItem, info.Subscribe) 77 | if err != nil { 78 | return fmt.Errorf("failed process subscribe operation for channel %s: %w", info.Name, err) 79 | } 80 | } 81 | 82 | r.SchemaEns().WithChannelsItem(info.Name, channelItem) 83 | 84 | return nil 85 | } 86 | 87 | func schemaToMap(schema jsonschema.Schema) map[string]interface{} { 88 | var m map[string]interface{} 89 | 90 | j, err := json.Marshal(schema) 91 | if err != nil { 92 | panic(err) 93 | } 94 | 95 | err = json.Unmarshal(j, &m) 96 | if err != nil { 97 | panic(err) 98 | } 99 | 100 | return m 101 | } 102 | 103 | func (r *Reflector) collectDefinition(name string, schema jsonschema.Schema) { 104 | if r.SchemaEns().ComponentsEns().Schemas == nil { 105 | r.SchemaEns().ComponentsEns().Schemas = make(map[string]map[string]interface{}, 1) 106 | } 107 | 108 | r.SchemaEns().ComponentsEns().Schemas[name] = schemaToMap(schema) 109 | } 110 | 111 | func (r *Reflector) makeOperation(channelItem *spec.ChannelItem, m *MessageSample) (*spec.Operation, error) { 112 | if m.MessageSample == nil { 113 | return &spec.Operation{ 114 | Message: &spec.Message{ 115 | Entity: &m.MessageEntity, 116 | }, 117 | }, nil 118 | } 119 | 120 | payloadSchema, err := r.Reflect(m.MessageSample, 121 | jsonschema.RootRef, 122 | jsonschema.DefinitionsPrefix("#/components/schemas/"), 123 | jsonschema.CollectDefinitions(r.collectDefinition), 124 | ) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | m.MessageEntity.Payload = schemaToMap(payloadSchema) 130 | 131 | headerSchema, err := r.Reflect(m.MessageSample, 132 | jsonschema.PropertyNameTag("header"), 133 | jsonschema.DefinitionsPrefix("#/components/schemas/"), 134 | jsonschema.CollectDefinitions(r.collectDefinition), 135 | ) 136 | if err != nil { 137 | return nil, err 138 | } 139 | 140 | if len(headerSchema.Properties) > 0 { 141 | m.MessageEntity.Headers = schemaToMap(headerSchema) 142 | } 143 | 144 | pathSchema, err := r.Reflect(m.MessageSample, 145 | jsonschema.PropertyNameTag("path"), 146 | jsonschema.DefinitionsPrefix("#/components/schemas/"), 147 | jsonschema.CollectDefinitions(r.collectDefinition), 148 | ) 149 | if err != nil { 150 | return nil, err 151 | } 152 | 153 | if len(pathSchema.Properties) > 0 { 154 | if channelItem.Parameters == nil { 155 | channelItem.Parameters = make(map[string]spec.Parameter, len(pathSchema.Properties)) 156 | } 157 | 158 | for name, paramSchema := range pathSchema.Properties { 159 | param := spec.Parameter{ 160 | Schema: schemaToMap(*paramSchema.TypeObjectEns()), 161 | } 162 | 163 | if payloadSchema.Description != nil { 164 | param.Description = *payloadSchema.Description 165 | } 166 | 167 | channelItem.Parameters[name] = param 168 | } 169 | } 170 | 171 | if payloadSchema.Ref != nil { 172 | messageName := strings.TrimPrefix(*payloadSchema.Ref, "#/components/schemas/") 173 | r.SchemaEns().ComponentsEns().WithMessagesItem(messageName, spec.Message{ 174 | Entity: &m.MessageEntity, 175 | }) 176 | 177 | return &spec.Operation{ 178 | Message: &spec.Message{ 179 | Reference: &spec.Reference{Ref: "#/components/messages/" + messageName}, 180 | }, 181 | }, nil 182 | } 183 | 184 | return &spec.Operation{ 185 | Message: &spec.Message{ 186 | Entity: &m.MessageEntity, 187 | }, 188 | }, nil 189 | } 190 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.0.0/reflect_test.go: -------------------------------------------------------------------------------- 1 | package asyncapi_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | "github.com/swaggest/assertjson" 11 | "github.com/swaggest/go-asyncapi/reflector/asyncapi-2.0.0" 12 | "github.com/swaggest/go-asyncapi/spec-2.0.0" 13 | ) 14 | 15 | func TestReflector_AddChannel(t *testing.T) { 16 | type SubItem struct { 17 | Key string `json:"key" description:"Item key"` 18 | Values []int64 `json:"values" uniqueItems:"true" description:"List of item values"` 19 | } 20 | 21 | type MyMessage struct { 22 | Name string `path:"name" description:"Name"` 23 | CreatedAt time.Time `json:"createdAt" description:"Creation time"` 24 | Items []SubItem `json:"items" description:"List of items"` 25 | } 26 | 27 | type MyAnotherMessage struct { 28 | TraceID string `header:"X-Trace-ID" description:"Tracing header" required:"true"` 29 | Item SubItem `json:"item" description:"Some item"` 30 | } 31 | 32 | r := asyncapi.Reflector{ 33 | Schema: &spec.AsyncAPI{ 34 | Servers: map[string]spec.Server{ 35 | "production": { 36 | URL: "api.lovely.com:{port}", 37 | Protocol: "amqp", 38 | ProtocolVersion: "AMQP 0.9.1", 39 | }, 40 | }, 41 | Info: spec.Info{ 42 | Version: "1.2.3", // required 43 | Title: "My Lovely Messaging API", 44 | }, 45 | }, 46 | } 47 | assert.NoError(t, r.AddChannel(asyncapi.ChannelInfo{ 48 | Name: "one.{name}.two", 49 | Publish: &asyncapi.MessageSample{ 50 | MessageEntity: spec.MessageEntity{ 51 | Description: "This is a sample schema", 52 | Summary: "Sample publisher", 53 | }, 54 | MessageSample: new(MyMessage), 55 | }, 56 | })) 57 | 58 | assert.NoError(t, r.AddChannel(asyncapi.ChannelInfo{ 59 | Name: "another.one", 60 | Subscribe: &asyncapi.MessageSample{ 61 | MessageEntity: spec.MessageEntity{ 62 | Description: "This is another sample schema", 63 | Summary: "Sample consumer", 64 | }, 65 | MessageSample: new(MyAnotherMessage), 66 | }, 67 | })) 68 | 69 | j, err := json.MarshalIndent(r.Schema, "", " ") 70 | require.NoError(t, err) 71 | 72 | assertjson.Equal(t, []byte(`{ 73 | "asyncapi": "2.0.0", 74 | "info": { 75 | "title": "My Lovely Messaging API", 76 | "version": "1.2.3" 77 | }, 78 | "servers": { 79 | "production": { 80 | "url": "api.lovely.com:{port}", 81 | "protocol": "amqp", 82 | "protocolVersion": "AMQP 0.9.1" 83 | } 84 | }, 85 | "channels": { 86 | "another.one": { 87 | "subscribe": { 88 | "message": { 89 | "$ref": "#/components/messages/Asyncapi200TestMyAnotherMessage" 90 | } 91 | } 92 | }, 93 | "one.{name}.two": { 94 | "parameters": { 95 | "name": { 96 | "schema": { 97 | "description": "Name", 98 | "type": "string" 99 | } 100 | } 101 | }, 102 | "publish": { 103 | "message": { 104 | "$ref": "#/components/messages/Asyncapi200TestMyMessage" 105 | } 106 | } 107 | } 108 | }, 109 | "components": { 110 | "schemas": { 111 | "Asyncapi200TestMyAnotherMessage": { 112 | "properties": { 113 | "item": { 114 | "$ref": "#/components/schemas/Asyncapi200TestSubItem", 115 | "description": "Some item" 116 | } 117 | }, 118 | "type": "object" 119 | }, 120 | "Asyncapi200TestMyMessage": { 121 | "properties": { 122 | "createdAt": { 123 | "description": "Creation time", 124 | "format": "date-time", 125 | "type": "string" 126 | }, 127 | "items": { 128 | "description": "List of items", 129 | "items": { 130 | "$ref": "#/components/schemas/Asyncapi200TestSubItem" 131 | }, 132 | "type": [ 133 | "array", 134 | "null" 135 | ] 136 | } 137 | }, 138 | "type": "object" 139 | }, 140 | "Asyncapi200TestSubItem": { 141 | "properties": { 142 | "key": { 143 | "description": "Item key", 144 | "type": "string" 145 | }, 146 | "values": { 147 | "description": "List of item values", 148 | "items": { 149 | "type": "integer" 150 | }, 151 | "type": [ 152 | "array", 153 | "null" 154 | ], 155 | "uniqueItems": true 156 | } 157 | }, 158 | "type": "object" 159 | } 160 | }, 161 | "messages": { 162 | "Asyncapi200TestMyAnotherMessage": { 163 | "headers": { 164 | "properties": { 165 | "X-Trace-ID": { 166 | "description": "Tracing header", 167 | "type": "string" 168 | } 169 | }, 170 | "required": [ 171 | "X-Trace-ID" 172 | ], 173 | "type": "object" 174 | }, 175 | "payload": { 176 | "$ref": "#/components/schemas/Asyncapi200TestMyAnotherMessage" 177 | }, 178 | "summary": "Sample consumer", 179 | "description": "This is another sample schema" 180 | }, 181 | "Asyncapi200TestMyMessage": { 182 | "payload": { 183 | "$ref": "#/components/schemas/Asyncapi200TestMyMessage" 184 | }, 185 | "summary": "Sample publisher", 186 | "description": "This is a sample schema" 187 | } 188 | } 189 | } 190 | }`), j, string(j)) 191 | } 192 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.0.0/sample.md: -------------------------------------------------------------------------------- 1 | # My Lovely Messaging API 1.2.3 documentation 2 | 3 | 4 | 5 | 6 | 7 | ## Table of Contents 8 | 9 | 10 | 11 | * [Servers](#servers) 12 | 13 | 14 | * [Channels](#channels) 15 | 16 | 17 | 18 | 19 | 20 | 21 | ## Servers 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 65 | 66 | 67 |
URLProtocolDescription
api.{country}.lovely.com:5672amqp0.9.1Production instance.
39 |
40 | URL Variables 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 59 | 60 | 61 | 62 |
NameDefault valuePossible valuesDescription
country 54 | US 55 | 57 |
  • RU
  • US
  • DE
  • FR
58 |
Country code.
63 |
64 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | ## Channels 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | #### Channel Parameters 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | ### `subscribe` another.one 93 | 94 | #### Message 95 | 96 | 97 | 98 | Sample consumer 99 | 100 | 101 | 102 | This is another sample schema. 103 | 104 | 105 | 106 | ##### Headers 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 |
NameTypeDescriptionAccepted values
X-Trace-ID string

Tracing header

127 |
Any
140 | 141 | 142 | 143 | ###### Example of headers _(generated)_ 144 | 145 | ```json 146 | { 147 | "X-Trace-ID": "string" 148 | } 149 | ``` 150 | 151 | 152 | 153 | 154 | ##### Payload 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 |
NameTypeDescriptionAccepted values
item object

Some item

175 |
Any
item.key string

Item key

187 |
Any
item.values array(integer)

List of item values

203 |
Any
222 | 223 | 224 | 225 | ###### Example of payload _(generated)_ 226 | 227 | ```json 228 | { 229 | "item": { 230 | "key": "string", 231 | "values": [ 232 | 0 233 | ] 234 | } 235 | } 236 | ``` 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | #### Channel Parameters 252 | 253 | 254 | 255 | ##### name 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 |
NameTypeDescriptionAccepted values
name string

Name

276 |
Any
289 | 290 | 291 | 292 | 293 | 294 | ### `publish` one.{name}.two 295 | 296 | #### Message 297 | 298 | 299 | 300 | Sample publisher 301 | 302 | 303 | 304 | This is a sample schema. 305 | 306 | 307 | 308 | 309 | 310 | ##### Payload 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 |
NameTypeDescriptionAccepted values
createdAt string

Creation time

331 |
Any
items array(object)

List of items

347 |
Any
items.key string

Item key

362 |
Any
items.values array(integer)

List of item values

378 |
Any
396 | 397 | 398 | 399 | ###### Example of payload _(generated)_ 400 | 401 | ```json 402 | { 403 | "createdAt": "2020-04-25T11:17:53Z", 404 | "items": [ 405 | { 406 | "key": "string", 407 | "values": [ 408 | 0 409 | ] 410 | } 411 | ] 412 | } 413 | ``` 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.0.0/sample.yaml: -------------------------------------------------------------------------------- 1 | asyncapi: 2.0.0 2 | info: 3 | title: My Lovely Messaging API 4 | version: 1.2.3 5 | servers: 6 | live: 7 | url: api.{country}.lovely.com:5672 8 | description: Production instance. 9 | protocol: amqp 10 | protocolVersion: 0.9.1 11 | variables: 12 | country: 13 | enum: 14 | - RU 15 | - US 16 | - DE 17 | - FR 18 | default: US 19 | description: Country code. 20 | channels: 21 | another.one: 22 | subscribe: 23 | message: 24 | $ref: '#/components/messages/Asyncapi200TestMyAnotherMessage' 25 | one.{name}.two: 26 | parameters: 27 | name: 28 | schema: 29 | description: Name 30 | type: string 31 | publish: 32 | message: 33 | $ref: '#/components/messages/Asyncapi200TestMyMessage' 34 | bindings: 35 | amqp: 36 | is: routingKey 37 | exchange: 38 | name: some-exchange 39 | components: 40 | schemas: 41 | Asyncapi200TestMyAnotherMessage: 42 | properties: 43 | item: 44 | $ref: '#/components/schemas/Asyncapi200TestSubItem' 45 | description: Some item 46 | type: object 47 | Asyncapi200TestMyMessage: 48 | properties: 49 | createdAt: 50 | description: Creation time 51 | format: date-time 52 | type: string 53 | items: 54 | description: List of items 55 | items: 56 | $ref: '#/components/schemas/Asyncapi200TestSubItem' 57 | type: 58 | - array 59 | - "null" 60 | type: object 61 | Asyncapi200TestSubItem: 62 | properties: 63 | key: 64 | description: Item key 65 | type: string 66 | values: 67 | description: List of item values 68 | items: 69 | type: integer 70 | type: 71 | - array 72 | - "null" 73 | uniqueItems: true 74 | type: object 75 | messages: 76 | Asyncapi200TestMyAnotherMessage: 77 | headers: 78 | properties: 79 | X-Trace-ID: 80 | description: Tracing header 81 | type: string 82 | required: 83 | - X-Trace-ID 84 | type: object 85 | payload: 86 | $ref: '#/components/schemas/Asyncapi200TestMyAnotherMessage' 87 | summary: Sample consumer 88 | description: This is another sample schema. 89 | Asyncapi200TestMyMessage: 90 | payload: 91 | $ref: '#/components/schemas/Asyncapi200TestMyMessage' 92 | summary: Sample publisher 93 | description: This is a sample schema. 94 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.1.0/example_test.go: -------------------------------------------------------------------------------- 1 | package asyncapi_test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/swaggest/go-asyncapi/reflector/asyncapi-2.1.0" 9 | "github.com/swaggest/go-asyncapi/spec-2.1.0" 10 | ) 11 | 12 | func ExampleReflector_AddChannel() { 13 | type SubItem struct { 14 | Key string `json:"key" description:"Item key"` 15 | Values []int64 `json:"values" uniqueItems:"true" description:"List of item values"` 16 | } 17 | 18 | type MyMessage struct { 19 | Name string `path:"name" description:"Name"` 20 | CreatedAt time.Time `json:"createdAt" description:"Creation time"` 21 | Items []SubItem `json:"items" description:"List of items"` 22 | } 23 | 24 | type MyAnotherMessage struct { 25 | TraceID string `header:"X-Trace-ID" description:"Tracing header" required:"true"` 26 | Item SubItem `json:"item" description:"Some item"` 27 | } 28 | 29 | reflector := asyncapi.Reflector{ 30 | Schema: &spec.AsyncAPI{ 31 | Servers: map[string]spec.Server{ 32 | "live": { 33 | URL: "api.{country}.lovely.com:5672", 34 | Description: "Production instance.", 35 | ProtocolVersion: "0.9.1", 36 | Protocol: "amqp", 37 | Variables: map[string]spec.ServerVariable{ 38 | "country": { 39 | Enum: []string{"RU", "US", "DE", "FR"}, 40 | Default: "US", 41 | Description: "Country code.", 42 | }, 43 | }, 44 | }, 45 | }, 46 | Info: spec.Info{ 47 | Version: "1.2.3", // required 48 | Title: "My Lovely Messaging API", 49 | }, 50 | }, 51 | } 52 | mustNotFail := func(err error) { 53 | if err != nil { 54 | panic(err.Error()) 55 | } 56 | } 57 | 58 | mustNotFail(reflector.AddChannel(asyncapi.ChannelInfo{ 59 | Name: "one.{name}.two", 60 | BaseChannelItem: &spec.ChannelItem{ 61 | Bindings: &spec.ChannelBindingsObject{ 62 | Amqp: &spec.AMQP091ChannelBindingObject{ 63 | Is: spec.AMQP091ChannelBindingObjectIsRoutingKey, 64 | Exchange: &spec.Exchange{ 65 | Name: "some-exchange", 66 | }, 67 | }, 68 | }, 69 | }, 70 | Publish: &asyncapi.MessageSample{ 71 | MessageEntity: spec.MessageEntity{ 72 | Description: "This is a sample schema.", 73 | Summary: "Sample publisher", 74 | }, 75 | MessageSample: new(MyMessage), 76 | }, 77 | })) 78 | 79 | mustNotFail(reflector.AddChannel(asyncapi.ChannelInfo{ 80 | Name: "another.one", 81 | Subscribe: &asyncapi.MessageSample{ 82 | MessageEntity: spec.MessageEntity{ 83 | Description: "This is another sample schema.", 84 | Summary: "Sample consumer", 85 | }, 86 | MessageSample: new(MyAnotherMessage), 87 | }, 88 | })) 89 | 90 | yaml, err := reflector.Schema.MarshalYAML() 91 | mustNotFail(err) 92 | 93 | fmt.Println(string(yaml)) 94 | mustNotFail(os.WriteFile("sample.yaml", yaml, 0o600)) 95 | // output: 96 | // asyncapi: 2.1.0 97 | // info: 98 | // title: My Lovely Messaging API 99 | // version: 1.2.3 100 | // servers: 101 | // live: 102 | // url: api.{country}.lovely.com:5672 103 | // description: Production instance. 104 | // protocol: amqp 105 | // protocolVersion: 0.9.1 106 | // variables: 107 | // country: 108 | // enum: 109 | // - RU 110 | // - US 111 | // - DE 112 | // - FR 113 | // default: US 114 | // description: Country code. 115 | // channels: 116 | // another.one: 117 | // subscribe: 118 | // message: 119 | // $ref: '#/components/messages/Asyncapi210TestMyAnotherMessage' 120 | // one.{name}.two: 121 | // parameters: 122 | // name: 123 | // schema: 124 | // description: Name 125 | // type: string 126 | // publish: 127 | // message: 128 | // $ref: '#/components/messages/Asyncapi210TestMyMessage' 129 | // bindings: 130 | // amqp: 131 | // is: routingKey 132 | // exchange: 133 | // name: some-exchange 134 | // components: 135 | // schemas: 136 | // Asyncapi210TestMyAnotherMessage: 137 | // properties: 138 | // item: 139 | // $ref: '#/components/schemas/Asyncapi210TestSubItem' 140 | // description: Some item 141 | // type: object 142 | // Asyncapi210TestMyMessage: 143 | // properties: 144 | // createdAt: 145 | // description: Creation time 146 | // format: date-time 147 | // type: string 148 | // items: 149 | // description: List of items 150 | // items: 151 | // $ref: '#/components/schemas/Asyncapi210TestSubItem' 152 | // type: 153 | // - array 154 | // - "null" 155 | // type: object 156 | // Asyncapi210TestSubItem: 157 | // properties: 158 | // key: 159 | // description: Item key 160 | // type: string 161 | // values: 162 | // description: List of item values 163 | // items: 164 | // type: integer 165 | // type: 166 | // - array 167 | // - "null" 168 | // uniqueItems: true 169 | // type: object 170 | // messages: 171 | // Asyncapi210TestMyAnotherMessage: 172 | // headers: 173 | // properties: 174 | // X-Trace-ID: 175 | // description: Tracing header 176 | // type: string 177 | // required: 178 | // - X-Trace-ID 179 | // type: object 180 | // payload: 181 | // $ref: '#/components/schemas/Asyncapi210TestMyAnotherMessage' 182 | // summary: Sample consumer 183 | // description: This is another sample schema. 184 | // Asyncapi210TestMyMessage: 185 | // payload: 186 | // $ref: '#/components/schemas/Asyncapi210TestMyMessage' 187 | // summary: Sample publisher 188 | // description: This is a sample schema. 189 | } 190 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.1.0/reflect.go: -------------------------------------------------------------------------------- 1 | // Package asyncapi provides schema reflector. 2 | package asyncapi 3 | 4 | import ( 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "strings" 9 | 10 | "github.com/swaggest/go-asyncapi/spec-2.1.0" 11 | "github.com/swaggest/jsonschema-go" 12 | ) 13 | 14 | // Reflector generates AsyncAPI definitions from provided message samples. 15 | type Reflector struct { 16 | jsonschema.Reflector 17 | Schema *spec.AsyncAPI 18 | } 19 | 20 | // DataEns ensures AsyncAPI Schema. 21 | // 22 | // Deprecated: use SchemaEns(). 23 | func (r *Reflector) DataEns() *spec.AsyncAPI { 24 | return r.SchemaEns() 25 | } 26 | 27 | // SchemaEns ensures AsyncAPI Schema. 28 | func (r *Reflector) SchemaEns() *spec.AsyncAPI { 29 | if r.Schema == nil { 30 | r.Schema = &spec.AsyncAPI{} 31 | } 32 | 33 | return r.Schema 34 | } 35 | 36 | // MessageSample is a structure that keeps general info and message sample (optional). 37 | type MessageSample struct { 38 | // MessageEntity holds general message info. 39 | spec.MessageEntity 40 | 41 | // MessageSample holds a sample of message to be converted to JSON Schema, e.g. `new(MyMessage)`. 42 | MessageSample interface{} 43 | } 44 | 45 | // ChannelInfo keeps user-defined information about channel. 46 | type ChannelInfo struct { 47 | Name string // event.{streetlightId}.lighting.measured 48 | Publish *MessageSample 49 | Subscribe *MessageSample 50 | BaseChannelItem *spec.ChannelItem // Optional, if set is used as a base to fill with Message data 51 | } 52 | 53 | // AddChannel adds user-defined channel to AsyncAPI definition. 54 | func (r *Reflector) AddChannel(info ChannelInfo) error { 55 | if info.Name == "" { 56 | return errors.New("name is required") 57 | } 58 | 59 | var ( 60 | channelItem = spec.ChannelItem{} 61 | err error 62 | ) 63 | 64 | if info.BaseChannelItem != nil { 65 | channelItem = *info.BaseChannelItem 66 | } 67 | 68 | if info.Publish != nil { 69 | channelItem.Publish, err = r.makeOperation(&channelItem, info.Publish) 70 | if err != nil { 71 | return fmt.Errorf("failed process publish operation for channel %s: %w", info.Name, err) 72 | } 73 | } 74 | 75 | if info.Subscribe != nil { 76 | channelItem.Subscribe, err = r.makeOperation(&channelItem, info.Subscribe) 77 | if err != nil { 78 | return fmt.Errorf("failed process subscribe operation for channel %s: %w", info.Name, err) 79 | } 80 | } 81 | 82 | r.SchemaEns().WithChannelsItem(info.Name, channelItem) 83 | 84 | return nil 85 | } 86 | 87 | func schemaToMap(schema jsonschema.Schema) map[string]interface{} { 88 | var m map[string]interface{} 89 | 90 | j, err := json.Marshal(schema) 91 | if err != nil { 92 | panic(err) 93 | } 94 | 95 | err = json.Unmarshal(j, &m) 96 | if err != nil { 97 | panic(err) 98 | } 99 | 100 | return m 101 | } 102 | 103 | func (r *Reflector) collectDefinition(name string, schema jsonschema.Schema) { 104 | if r.SchemaEns().ComponentsEns().Schemas == nil { 105 | r.SchemaEns().ComponentsEns().Schemas = make(map[string]map[string]interface{}, 1) 106 | } 107 | 108 | r.SchemaEns().ComponentsEns().Schemas[name] = schemaToMap(schema) 109 | } 110 | 111 | func (r *Reflector) makeOperation(channelItem *spec.ChannelItem, m *MessageSample) (*spec.Operation, error) { 112 | if m.MessageSample == nil { 113 | op := spec.Operation{} 114 | op.MessageEns().OneOf1Ens().WithMessageEntity(m.MessageEntity) 115 | 116 | return &op, nil 117 | } 118 | 119 | payloadSchema, err := r.Reflect(m.MessageSample, 120 | jsonschema.RootRef, 121 | jsonschema.DefinitionsPrefix("#/components/schemas/"), 122 | jsonschema.CollectDefinitions(r.collectDefinition), 123 | ) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | m.MessageEntity.Payload = schemaToMap(payloadSchema) 129 | 130 | headerSchema, err := r.Reflect(m.MessageSample, 131 | jsonschema.PropertyNameTag("header"), 132 | jsonschema.DefinitionsPrefix("#/components/schemas/"), 133 | jsonschema.CollectDefinitions(r.collectDefinition), 134 | ) 135 | if err != nil { 136 | return nil, err 137 | } 138 | 139 | if len(headerSchema.Properties) > 0 { 140 | m.MessageEntity.HeadersEns().WithSchema(schemaToMap(headerSchema)) 141 | } 142 | 143 | pathSchema, err := r.Reflect(m.MessageSample, 144 | jsonschema.PropertyNameTag("path"), 145 | jsonschema.DefinitionsPrefix("#/components/schemas/"), 146 | jsonschema.CollectDefinitions(r.collectDefinition), 147 | ) 148 | if err != nil { 149 | return nil, err 150 | } 151 | 152 | if len(pathSchema.Properties) > 0 { 153 | if channelItem.Parameters == nil { 154 | channelItem.Parameters = make(map[string]spec.Parameter, len(pathSchema.Properties)) 155 | } 156 | 157 | for name, paramSchema := range pathSchema.Properties { 158 | param := spec.Parameter{ 159 | Schema: schemaToMap(*paramSchema.TypeObjectEns()), 160 | } 161 | 162 | if payloadSchema.Description != nil { 163 | param.Description = *payloadSchema.Description 164 | } 165 | 166 | channelItem.Parameters[name] = param 167 | } 168 | } 169 | 170 | if payloadSchema.Ref != nil { 171 | messageName := strings.TrimPrefix(*payloadSchema.Ref, "#/components/schemas/") 172 | msg := spec.Message{} 173 | msg.OneOf1Ens().WithMessageEntity(m.MessageEntity) 174 | 175 | r.SchemaEns().ComponentsEns().WithMessagesItem(messageName, msg) 176 | 177 | return &spec.Operation{ 178 | Message: &spec.Message{ 179 | Reference: &spec.Reference{Ref: "#/components/messages/" + messageName}, 180 | }, 181 | }, nil 182 | } 183 | 184 | msg := spec.Message{} 185 | msg.OneOf1Ens().WithMessageEntity(m.MessageEntity) 186 | 187 | return &spec.Operation{ 188 | Message: &msg, 189 | }, nil 190 | } 191 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.1.0/reflect_test.go: -------------------------------------------------------------------------------- 1 | package asyncapi_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/swaggest/assertjson" 9 | "github.com/swaggest/go-asyncapi/reflector/asyncapi-2.1.0" 10 | "github.com/swaggest/go-asyncapi/spec-2.1.0" 11 | ) 12 | 13 | func TestReflector_AddChannel(t *testing.T) { 14 | type SubItem struct { 15 | Key string `json:"key" description:"Item key"` 16 | Values []int64 `json:"values" uniqueItems:"true" description:"List of item values"` 17 | } 18 | 19 | type MyMessage struct { 20 | Name string `path:"name" description:"Name"` 21 | CreatedAt time.Time `json:"createdAt" description:"Creation time"` 22 | Items []SubItem `json:"items" description:"List of items"` 23 | } 24 | 25 | type MyAnotherMessage struct { 26 | TraceID string `header:"X-Trace-ID" description:"Tracing header" required:"true"` 27 | Item SubItem `json:"item" description:"Some item"` 28 | } 29 | 30 | r := asyncapi.Reflector{ 31 | Schema: &spec.AsyncAPI{ 32 | Servers: map[string]spec.Server{ 33 | "production": { 34 | URL: "api.lovely.com:{port}", 35 | Protocol: "amqp", 36 | ProtocolVersion: "AMQP 0.9.1", 37 | }, 38 | }, 39 | Info: spec.Info{ 40 | Version: "1.2.3", // required 41 | Title: "My Lovely Messaging API", 42 | }, 43 | }, 44 | } 45 | assert.NoError(t, r.AddChannel(asyncapi.ChannelInfo{ 46 | Name: "one.{name}.two", 47 | Publish: &asyncapi.MessageSample{ 48 | MessageEntity: spec.MessageEntity{ 49 | Description: "This is a sample schema", 50 | Summary: "Sample publisher", 51 | }, 52 | MessageSample: new(MyMessage), 53 | }, 54 | })) 55 | 56 | assert.NoError(t, r.AddChannel(asyncapi.ChannelInfo{ 57 | Name: "another.one", 58 | Subscribe: &asyncapi.MessageSample{ 59 | MessageEntity: spec.MessageEntity{ 60 | Description: "This is another sample schema", 61 | Summary: "Sample consumer", 62 | }, 63 | MessageSample: new(MyAnotherMessage), 64 | }, 65 | })) 66 | 67 | assertjson.EqualMarshal(t, []byte(`{ 68 | "asyncapi": "2.1.0", 69 | "info": { 70 | "title": "My Lovely Messaging API", 71 | "version": "1.2.3" 72 | }, 73 | "servers": { 74 | "production": { 75 | "url": "api.lovely.com:{port}", 76 | "protocol": "amqp", 77 | "protocolVersion": "AMQP 0.9.1" 78 | } 79 | }, 80 | "channels": { 81 | "another.one": { 82 | "subscribe": { 83 | "message": { 84 | "$ref": "#/components/messages/Asyncapi210TestMyAnotherMessage" 85 | } 86 | } 87 | }, 88 | "one.{name}.two": { 89 | "parameters": { 90 | "name": { 91 | "schema": { 92 | "description": "Name", 93 | "type": "string" 94 | } 95 | } 96 | }, 97 | "publish": { 98 | "message": { 99 | "$ref": "#/components/messages/Asyncapi210TestMyMessage" 100 | } 101 | } 102 | } 103 | }, 104 | "components": { 105 | "schemas": { 106 | "Asyncapi210TestMyAnotherMessage": { 107 | "properties": { 108 | "item": { 109 | "$ref": "#/components/schemas/Asyncapi210TestSubItem", 110 | "description": "Some item" 111 | } 112 | }, 113 | "type": "object" 114 | }, 115 | "Asyncapi210TestMyMessage": { 116 | "properties": { 117 | "createdAt": { 118 | "description": "Creation time", 119 | "format": "date-time", 120 | "type": "string" 121 | }, 122 | "items": { 123 | "description": "List of items", 124 | "items": { 125 | "$ref": "#/components/schemas/Asyncapi210TestSubItem" 126 | }, 127 | "type": [ 128 | "array", 129 | "null" 130 | ] 131 | } 132 | }, 133 | "type": "object" 134 | }, 135 | "Asyncapi210TestSubItem": { 136 | "properties": { 137 | "key": { 138 | "description": "Item key", 139 | "type": "string" 140 | }, 141 | "values": { 142 | "description": "List of item values", 143 | "items": { 144 | "type": "integer" 145 | }, 146 | "type": [ 147 | "array", 148 | "null" 149 | ], 150 | "uniqueItems": true 151 | } 152 | }, 153 | "type": "object" 154 | } 155 | }, 156 | "messages": { 157 | "Asyncapi210TestMyAnotherMessage": { 158 | "headers": { 159 | "properties": { 160 | "X-Trace-ID": { 161 | "description": "Tracing header", 162 | "type": "string" 163 | } 164 | }, 165 | "required": [ 166 | "X-Trace-ID" 167 | ], 168 | "type": "object" 169 | }, 170 | "payload": { 171 | "$ref": "#/components/schemas/Asyncapi210TestMyAnotherMessage" 172 | }, 173 | "summary": "Sample consumer", 174 | "description": "This is another sample schema" 175 | }, 176 | "Asyncapi210TestMyMessage": { 177 | "payload": { 178 | "$ref": "#/components/schemas/Asyncapi210TestMyMessage" 179 | }, 180 | "summary": "Sample publisher", 181 | "description": "This is a sample schema" 182 | } 183 | } 184 | } 185 | } 186 | `), r.Schema) 187 | } 188 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.1.0/sample.yaml: -------------------------------------------------------------------------------- 1 | asyncapi: 2.1.0 2 | info: 3 | title: My Lovely Messaging API 4 | version: 1.2.3 5 | servers: 6 | live: 7 | url: api.{country}.lovely.com:5672 8 | description: Production instance. 9 | protocol: amqp 10 | protocolVersion: 0.9.1 11 | variables: 12 | country: 13 | enum: 14 | - RU 15 | - US 16 | - DE 17 | - FR 18 | default: US 19 | description: Country code. 20 | channels: 21 | another.one: 22 | subscribe: 23 | message: 24 | $ref: '#/components/messages/Asyncapi210TestMyAnotherMessage' 25 | one.{name}.two: 26 | parameters: 27 | name: 28 | schema: 29 | description: Name 30 | type: string 31 | publish: 32 | message: 33 | $ref: '#/components/messages/Asyncapi210TestMyMessage' 34 | bindings: 35 | amqp: 36 | is: routingKey 37 | exchange: 38 | name: some-exchange 39 | components: 40 | schemas: 41 | Asyncapi210TestMyAnotherMessage: 42 | properties: 43 | item: 44 | $ref: '#/components/schemas/Asyncapi210TestSubItem' 45 | description: Some item 46 | type: object 47 | Asyncapi210TestMyMessage: 48 | properties: 49 | createdAt: 50 | description: Creation time 51 | format: date-time 52 | type: string 53 | items: 54 | description: List of items 55 | items: 56 | $ref: '#/components/schemas/Asyncapi210TestSubItem' 57 | type: 58 | - array 59 | - "null" 60 | type: object 61 | Asyncapi210TestSubItem: 62 | properties: 63 | key: 64 | description: Item key 65 | type: string 66 | values: 67 | description: List of item values 68 | items: 69 | type: integer 70 | type: 71 | - array 72 | - "null" 73 | uniqueItems: true 74 | type: object 75 | messages: 76 | Asyncapi210TestMyAnotherMessage: 77 | headers: 78 | properties: 79 | X-Trace-ID: 80 | description: Tracing header 81 | type: string 82 | required: 83 | - X-Trace-ID 84 | type: object 85 | payload: 86 | $ref: '#/components/schemas/Asyncapi210TestMyAnotherMessage' 87 | summary: Sample consumer 88 | description: This is another sample schema. 89 | Asyncapi210TestMyMessage: 90 | payload: 91 | $ref: '#/components/schemas/Asyncapi210TestMyMessage' 92 | summary: Sample publisher 93 | description: This is a sample schema. 94 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.4.0/example_test.go: -------------------------------------------------------------------------------- 1 | package asyncapi_test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | 8 | "github.com/swaggest/go-asyncapi/reflector/asyncapi-2.4.0" 9 | "github.com/swaggest/go-asyncapi/spec-2.4.0" 10 | ) 11 | 12 | func ExampleReflector_AddChannel_amqp() { 13 | type SubItem struct { 14 | Key string `json:"key" description:"Item key"` 15 | Values []int64 `json:"values" uniqueItems:"true" description:"List of item values"` 16 | } 17 | 18 | type MyMessage struct { 19 | Name string `path:"name" description:"Name"` 20 | CreatedAt time.Time `json:"createdAt" description:"Creation time"` 21 | Items []SubItem `json:"items" description:"List of items"` 22 | } 23 | 24 | type MyAnotherMessage struct { 25 | TraceID string `header:"X-Trace-ID" description:"Tracing header" required:"true"` 26 | Item SubItem `json:"item" description:"Some item"` 27 | } 28 | 29 | asyncAPI := spec.AsyncAPI{} 30 | asyncAPI.Info.Version = "1.2.3" 31 | asyncAPI.Info.Title = "My Lovely Messaging API" 32 | 33 | asyncAPI.AddServer("live", spec.Server{ 34 | URL: "api.{country}.lovely.com:5672", 35 | Description: "Production instance.", 36 | ProtocolVersion: "0.9.1", 37 | Protocol: "amqp", 38 | Variables: map[string]spec.ServerVariable{ 39 | "country": { 40 | Enum: []string{"RU", "US", "DE", "FR"}, 41 | Default: "US", 42 | Description: "Country code.", 43 | }, 44 | }, 45 | }) 46 | 47 | reflector := asyncapi.Reflector{} 48 | reflector.Schema = &asyncAPI 49 | 50 | mustNotFail := func(err error) { 51 | if err != nil { 52 | panic(err.Error()) 53 | } 54 | } 55 | 56 | mustNotFail(reflector.AddChannel(asyncapi.ChannelInfo{ 57 | Name: "one.{name}.two", 58 | BaseChannelItem: &spec.ChannelItem{ 59 | Bindings: &spec.ChannelBindingsObject{ 60 | Amqp: &spec.AmqpChannel{ 61 | Is: spec.AmqpChannelIsRoutingKey, 62 | Exchange: &spec.AmqpChannelExchange{ 63 | Name: "some-exchange", 64 | }, 65 | }, 66 | }, 67 | }, 68 | Publish: &asyncapi.MessageSample{ 69 | MessageEntity: spec.MessageEntity{ 70 | Description: "This is a sample schema.", 71 | Summary: "Sample publisher", 72 | }, 73 | MessageSample: new(MyMessage), 74 | }, 75 | })) 76 | 77 | mustNotFail(reflector.AddChannel(asyncapi.ChannelInfo{ 78 | Name: "another.one", 79 | Subscribe: &asyncapi.MessageSample{ 80 | MessageEntity: spec.MessageEntity{ 81 | Description: "This is another sample schema.", 82 | Summary: "Sample consumer", 83 | }, 84 | MessageSample: new(MyAnotherMessage), 85 | }, 86 | })) 87 | 88 | yaml, err := reflector.Schema.MarshalYAML() 89 | mustNotFail(err) 90 | 91 | fmt.Println(string(yaml)) 92 | mustNotFail(os.WriteFile("sample-amqp.yaml", yaml, 0o600)) 93 | // output: 94 | // asyncapi: 2.4.0 95 | // info: 96 | // title: My Lovely Messaging API 97 | // version: 1.2.3 98 | // servers: 99 | // live: 100 | // url: api.{country}.lovely.com:5672 101 | // description: Production instance. 102 | // protocol: amqp 103 | // protocolVersion: 0.9.1 104 | // variables: 105 | // country: 106 | // enum: 107 | // - RU 108 | // - US 109 | // - DE 110 | // - FR 111 | // default: US 112 | // description: Country code. 113 | // channels: 114 | // another.one: 115 | // subscribe: 116 | // message: 117 | // $ref: '#/components/messages/Asyncapi240TestMyAnotherMessage' 118 | // one.{name}.two: 119 | // parameters: 120 | // name: 121 | // schema: 122 | // description: Name 123 | // type: string 124 | // publish: 125 | // message: 126 | // $ref: '#/components/messages/Asyncapi240TestMyMessage' 127 | // bindings: 128 | // amqp: 129 | // bindingVersion: 0.2.0 130 | // is: routingKey 131 | // exchange: 132 | // name: some-exchange 133 | // components: 134 | // schemas: 135 | // Asyncapi240TestMyAnotherMessage: 136 | // properties: 137 | // item: 138 | // $ref: '#/components/schemas/Asyncapi240TestSubItem' 139 | // description: Some item 140 | // type: object 141 | // Asyncapi240TestMyMessage: 142 | // properties: 143 | // createdAt: 144 | // description: Creation time 145 | // format: date-time 146 | // type: string 147 | // items: 148 | // description: List of items 149 | // items: 150 | // $ref: '#/components/schemas/Asyncapi240TestSubItem' 151 | // type: 152 | // - array 153 | // - "null" 154 | // type: object 155 | // Asyncapi240TestSubItem: 156 | // properties: 157 | // key: 158 | // description: Item key 159 | // type: string 160 | // values: 161 | // description: List of item values 162 | // items: 163 | // type: integer 164 | // type: 165 | // - array 166 | // - "null" 167 | // uniqueItems: true 168 | // type: object 169 | // messages: 170 | // Asyncapi240TestMyAnotherMessage: 171 | // headers: 172 | // properties: 173 | // X-Trace-ID: 174 | // description: Tracing header 175 | // type: string 176 | // required: 177 | // - X-Trace-ID 178 | // type: object 179 | // payload: 180 | // $ref: '#/components/schemas/Asyncapi240TestMyAnotherMessage' 181 | // summary: Sample consumer 182 | // description: This is another sample schema. 183 | // Asyncapi240TestMyMessage: 184 | // payload: 185 | // $ref: '#/components/schemas/Asyncapi240TestMyMessage' 186 | // summary: Sample publisher 187 | // description: This is a sample schema. 188 | } 189 | 190 | func ExampleReflector_AddChannel_kafka() { 191 | type SubItem struct { 192 | Key string `json:"key" description:"Item key"` 193 | Values []int64 `json:"values" uniqueItems:"true" description:"List of item values"` 194 | } 195 | 196 | type MyMessage struct { 197 | Name string `path:"name" description:"Name"` 198 | CreatedAt time.Time `json:"createdAt" description:"Creation time"` 199 | Items []SubItem `json:"items" description:"List of items"` 200 | } 201 | 202 | type MyAnotherMessage struct { 203 | TraceID string `header:"X-Trace-ID" description:"Tracing header" required:"true"` 204 | Item SubItem `json:"item" description:"Some item"` 205 | } 206 | 207 | asyncAPI := spec.AsyncAPI{} 208 | asyncAPI.Info.Version = "1.2.3" 209 | asyncAPI.Info.Title = "My Lovely Messaging API" 210 | 211 | asyncAPI.AddServer("live", spec.Server{ 212 | URL: "api.{country}.lovely.com:5672", 213 | Description: "Production instance.", 214 | ProtocolVersion: "1.0.0", 215 | Protocol: "kafka", 216 | Variables: map[string]spec.ServerVariable{ 217 | "country": { 218 | Enum: []string{"RU", "US", "DE", "FR"}, 219 | Default: "US", 220 | Description: "Country code.", 221 | }, 222 | }, 223 | }) 224 | 225 | reflector := asyncapi.Reflector{} 226 | reflector.Schema = &asyncAPI 227 | 228 | mustNotFail := func(err error) { 229 | if err != nil { 230 | panic(err.Error()) 231 | } 232 | } 233 | 234 | mustNotFail(reflector.AddChannel(asyncapi.ChannelInfo{ 235 | Name: "one.{name}.two", 236 | BaseOperation: &spec.Operation{ 237 | Bindings: &spec.OperationBindingsObject{ 238 | Kafka: &spec.KafkaOperation{ 239 | GroupID: (&spec.KafkaOperationGroupID{}).WithStringProperty("my-group-id"), 240 | ClientID: (&spec.KafkaOperationClientID{}).WithStringProperty("my-client-id"), 241 | }, 242 | }, 243 | }, 244 | Publish: &asyncapi.MessageSample{ 245 | MessageEntity: spec.MessageEntity{ 246 | Description: "This is a sample schema.", 247 | Summary: "Sample publisher", 248 | Bindings: &spec.MessageBindingsObject{ 249 | Kafka: &spec.KafkaMessage{ 250 | Key: (&spec.KafkaMessageKey{}).WithStringProperty("my-key"), 251 | }, 252 | }, 253 | }, 254 | MessageSample: new(MyMessage), 255 | }, 256 | })) 257 | 258 | mustNotFail(reflector.AddChannel(asyncapi.ChannelInfo{ 259 | Name: "another.one", 260 | BaseOperation: &spec.Operation{ 261 | Bindings: &spec.OperationBindingsObject{ 262 | Kafka: &spec.KafkaOperation{ 263 | GroupID: (&spec.KafkaOperationGroupID{}).WithStringProperty("my-group-id-2"), 264 | ClientID: (&spec.KafkaOperationClientID{}).WithStringProperty("my-client-id"), 265 | }, 266 | }, 267 | }, 268 | Subscribe: &asyncapi.MessageSample{ 269 | MessageEntity: spec.MessageEntity{ 270 | Description: "This is another sample schema.", 271 | Summary: "Sample consumer", 272 | }, 273 | MessageSample: new(MyAnotherMessage), 274 | }, 275 | })) 276 | 277 | yaml, err := reflector.Schema.MarshalYAML() 278 | mustNotFail(err) 279 | 280 | fmt.Println(string(yaml)) 281 | mustNotFail(os.WriteFile("sample-kafka.yaml", yaml, 0o600)) 282 | // output: 283 | // asyncapi: 2.4.0 284 | // info: 285 | // title: My Lovely Messaging API 286 | // version: 1.2.3 287 | // servers: 288 | // live: 289 | // url: api.{country}.lovely.com:5672 290 | // description: Production instance. 291 | // protocol: kafka 292 | // protocolVersion: 1.0.0 293 | // variables: 294 | // country: 295 | // enum: 296 | // - RU 297 | // - US 298 | // - DE 299 | // - FR 300 | // default: US 301 | // description: Country code. 302 | // channels: 303 | // another.one: 304 | // subscribe: 305 | // bindings: 306 | // kafka: 307 | // bindingVersion: 0.3.0 308 | // groupId: my-group-id-2 309 | // clientId: my-client-id 310 | // message: 311 | // $ref: '#/components/messages/Asyncapi240TestMyAnotherMessage' 312 | // one.{name}.two: 313 | // parameters: 314 | // name: 315 | // schema: 316 | // description: Name 317 | // type: string 318 | // publish: 319 | // bindings: 320 | // kafka: 321 | // bindingVersion: 0.3.0 322 | // groupId: my-group-id 323 | // clientId: my-client-id 324 | // message: 325 | // $ref: '#/components/messages/Asyncapi240TestMyMessage' 326 | // components: 327 | // schemas: 328 | // Asyncapi240TestMyAnotherMessage: 329 | // properties: 330 | // item: 331 | // $ref: '#/components/schemas/Asyncapi240TestSubItem' 332 | // description: Some item 333 | // type: object 334 | // Asyncapi240TestMyMessage: 335 | // properties: 336 | // createdAt: 337 | // description: Creation time 338 | // format: date-time 339 | // type: string 340 | // items: 341 | // description: List of items 342 | // items: 343 | // $ref: '#/components/schemas/Asyncapi240TestSubItem' 344 | // type: 345 | // - array 346 | // - "null" 347 | // type: object 348 | // Asyncapi240TestSubItem: 349 | // properties: 350 | // key: 351 | // description: Item key 352 | // type: string 353 | // values: 354 | // description: List of item values 355 | // items: 356 | // type: integer 357 | // type: 358 | // - array 359 | // - "null" 360 | // uniqueItems: true 361 | // type: object 362 | // messages: 363 | // Asyncapi240TestMyAnotherMessage: 364 | // headers: 365 | // properties: 366 | // X-Trace-ID: 367 | // description: Tracing header 368 | // type: string 369 | // required: 370 | // - X-Trace-ID 371 | // type: object 372 | // payload: 373 | // $ref: '#/components/schemas/Asyncapi240TestMyAnotherMessage' 374 | // summary: Sample consumer 375 | // description: This is another sample schema. 376 | // Asyncapi240TestMyMessage: 377 | // payload: 378 | // $ref: '#/components/schemas/Asyncapi240TestMyMessage' 379 | // summary: Sample publisher 380 | // description: This is a sample schema. 381 | // bindings: 382 | // kafka: 383 | // bindingVersion: 0.3.0 384 | // key: my-key 385 | } 386 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.4.0/reflect.go: -------------------------------------------------------------------------------- 1 | // Package asyncapi provides schema reflector. 2 | package asyncapi 3 | 4 | import ( 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "strings" 9 | 10 | "github.com/swaggest/go-asyncapi/spec-2.4.0" 11 | "github.com/swaggest/jsonschema-go" 12 | ) 13 | 14 | // Reflector generates AsyncAPI definitions from provided message samples. 15 | type Reflector struct { 16 | jsonschema.Reflector 17 | Schema *spec.AsyncAPI 18 | } 19 | 20 | // DataEns ensures AsyncAPI Schema. 21 | // 22 | // Deprecated: use SchemaEns(). 23 | func (r *Reflector) DataEns() *spec.AsyncAPI { 24 | return r.SchemaEns() 25 | } 26 | 27 | // SchemaEns ensures AsyncAPI Schema. 28 | func (r *Reflector) SchemaEns() *spec.AsyncAPI { 29 | if r.Schema == nil { 30 | r.Schema = &spec.AsyncAPI{} 31 | } 32 | 33 | return r.Schema 34 | } 35 | 36 | // MessageSample is a structure that keeps general info and message sample (optional). 37 | type MessageSample struct { 38 | // MessageEntity holds general message info. 39 | spec.MessageEntity 40 | 41 | // MessageSample holds a sample of message to be converted to JSON Schema, e.g. `new(MyMessage)`. 42 | MessageSample interface{} 43 | } 44 | 45 | // ChannelInfo keeps user-defined information about channel. 46 | type ChannelInfo struct { 47 | Name string // event.{streetlightId}.lighting.measured 48 | Publish *MessageSample 49 | Subscribe *MessageSample 50 | BaseChannelItem *spec.ChannelItem // Optional, if set is used as a base to fill with Message data. 51 | BaseOperation *spec.Operation // Optional, if set is used as a base to fill Operation data. 52 | } 53 | 54 | // AddChannel adds user-defined channel to AsyncAPI definition. 55 | func (r *Reflector) AddChannel(info ChannelInfo) error { 56 | if info.Name == "" { 57 | return errors.New("name is required") 58 | } 59 | 60 | var ( 61 | channelItem = spec.ChannelItem{} 62 | operation = spec.Operation{} 63 | err error 64 | ) 65 | 66 | if info.BaseChannelItem != nil { 67 | channelItem = *info.BaseChannelItem 68 | } 69 | 70 | if info.BaseOperation != nil { 71 | operation = *info.BaseOperation 72 | } 73 | 74 | if info.Publish != nil { 75 | channelItem.Publish, err = r.makeOperation(&channelItem, info.Publish, operation) 76 | if err != nil { 77 | return fmt.Errorf("failed process publish operation for channel %s: %w", info.Name, err) 78 | } 79 | } 80 | 81 | if info.Subscribe != nil { 82 | channelItem.Subscribe, err = r.makeOperation(&channelItem, info.Subscribe, operation) 83 | if err != nil { 84 | return fmt.Errorf("failed process subscribe operation for channel %s: %w", info.Name, err) 85 | } 86 | } 87 | 88 | r.SchemaEns().WithChannelsItem(info.Name, channelItem) 89 | 90 | return nil 91 | } 92 | 93 | func schemaToMap(schema jsonschema.Schema) map[string]interface{} { 94 | var m map[string]interface{} 95 | 96 | j, err := json.Marshal(schema) 97 | if err != nil { 98 | panic(err) 99 | } 100 | 101 | err = json.Unmarshal(j, &m) 102 | if err != nil { 103 | panic(err) 104 | } 105 | 106 | return m 107 | } 108 | 109 | func (r *Reflector) collectDefinition(name string, schema jsonschema.Schema) { 110 | if r.SchemaEns().ComponentsEns().Schemas == nil { 111 | r.SchemaEns().ComponentsEns().Schemas = make(map[string]map[string]interface{}, 1) 112 | } 113 | 114 | r.SchemaEns().ComponentsEns().Schemas[name] = schemaToMap(schema) 115 | } 116 | 117 | func (r *Reflector) makeOperation(channelItem *spec.ChannelItem, m *MessageSample, op spec.Operation) (*spec.Operation, error) { 118 | if m.MessageSample == nil { 119 | op.MessageEns().OneOf1Ens().WithMessageEntity(m.MessageEntity) 120 | 121 | return &op, nil 122 | } 123 | 124 | payloadSchema, err := r.Reflect(m.MessageSample, 125 | jsonschema.RootRef, 126 | jsonschema.DefinitionsPrefix("#/components/schemas/"), 127 | jsonschema.CollectDefinitions(r.collectDefinition), 128 | ) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | m.MessageEntity.Payload = schemaToMap(payloadSchema) 134 | 135 | headerSchema, err := r.Reflect(m.MessageSample, 136 | jsonschema.PropertyNameTag("header"), 137 | jsonschema.DefinitionsPrefix("#/components/schemas/"), 138 | jsonschema.CollectDefinitions(r.collectDefinition), 139 | ) 140 | if err != nil { 141 | return nil, err 142 | } 143 | 144 | if len(headerSchema.Properties) > 0 { 145 | m.MessageEntity.HeadersEns().WithSchema(schemaToMap(headerSchema)) 146 | } 147 | 148 | pathSchema, err := r.Reflect(m.MessageSample, 149 | jsonschema.PropertyNameTag("path"), 150 | jsonschema.DefinitionsPrefix("#/components/schemas/"), 151 | jsonschema.CollectDefinitions(r.collectDefinition), 152 | ) 153 | if err != nil { 154 | return nil, err 155 | } 156 | 157 | if len(pathSchema.Properties) > 0 { 158 | if channelItem.Parameters == nil { 159 | channelItem.Parameters = make(map[string]spec.Parameter, len(pathSchema.Properties)) 160 | } 161 | 162 | for name, paramSchema := range pathSchema.Properties { 163 | param := spec.Parameter{ 164 | Schema: schemaToMap(*paramSchema.TypeObjectEns()), 165 | } 166 | 167 | if payloadSchema.Description != nil { 168 | param.Description = *payloadSchema.Description 169 | } 170 | 171 | channelItem.Parameters[name] = param 172 | } 173 | } 174 | 175 | if payloadSchema.Ref != nil { 176 | messageName := strings.TrimPrefix(*payloadSchema.Ref, "#/components/schemas/") 177 | msg := spec.Message{} 178 | msg.OneOf1Ens().WithMessageEntity(m.MessageEntity) 179 | 180 | r.SchemaEns().ComponentsEns().WithMessagesItem(messageName, msg) 181 | 182 | op.Message = &spec.Message{ 183 | Reference: &spec.Reference{Ref: "#/components/messages/" + messageName}, 184 | } 185 | 186 | return &op, nil 187 | } 188 | 189 | msg := spec.Message{} 190 | msg.OneOf1Ens().WithMessageEntity(m.MessageEntity) 191 | op.Message = &msg 192 | 193 | return &op, nil 194 | } 195 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.4.0/reflect_test.go: -------------------------------------------------------------------------------- 1 | package asyncapi_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/swaggest/assertjson" 9 | "github.com/swaggest/go-asyncapi/reflector/asyncapi-2.4.0" 10 | "github.com/swaggest/go-asyncapi/spec-2.4.0" 11 | ) 12 | 13 | func TestReflector_AddChannel(t *testing.T) { 14 | type SubItem struct { 15 | Key string `json:"key" description:"Item key"` 16 | Values []int64 `json:"values" uniqueItems:"true" description:"List of item values"` 17 | } 18 | 19 | type MyMessage struct { 20 | Name string `path:"name" description:"Name"` 21 | CreatedAt time.Time `json:"createdAt" description:"Creation time"` 22 | Items []SubItem `json:"items" description:"List of items"` 23 | } 24 | 25 | type MyAnotherMessage struct { 26 | TraceID string `header:"X-Trace-ID" description:"Tracing header" required:"true"` 27 | Item SubItem `json:"item" description:"Some item"` 28 | } 29 | 30 | asyncAPI := spec.AsyncAPI{} 31 | asyncAPI.AddServer("production", spec.Server{ 32 | URL: "api.lovely.com:{port}", 33 | Protocol: "amqp", 34 | ProtocolVersion: "AMQP 0.9.1", 35 | }) 36 | 37 | asyncAPI.Info.Version = "1.2.3" 38 | asyncAPI.Info.Title = "My Lovely Messaging API" 39 | 40 | r := asyncapi.Reflector{Schema: &asyncAPI} 41 | assert.NoError(t, r.AddChannel(asyncapi.ChannelInfo{ 42 | Name: "one.{name}.two", 43 | Publish: &asyncapi.MessageSample{ 44 | MessageEntity: spec.MessageEntity{ 45 | Description: "This is a sample schema", 46 | Summary: "Sample publisher", 47 | }, 48 | MessageSample: new(MyMessage), 49 | }, 50 | })) 51 | 52 | assert.NoError(t, r.AddChannel(asyncapi.ChannelInfo{ 53 | Name: "another.one", 54 | Subscribe: &asyncapi.MessageSample{ 55 | MessageEntity: spec.MessageEntity{ 56 | Description: "This is another sample schema", 57 | Summary: "Sample consumer", 58 | }, 59 | MessageSample: new(MyAnotherMessage), 60 | }, 61 | })) 62 | 63 | assertjson.EqualMarshal(t, []byte(`{ 64 | "asyncapi":"2.4.0", 65 | "info":{"title":"My Lovely Messaging API","version":"1.2.3"}, 66 | "servers":{ 67 | "production":{ 68 | "url":"api.lovely.com:{port}","protocol":"amqp", 69 | "protocolVersion":"AMQP 0.9.1" 70 | } 71 | }, 72 | "channels":{ 73 | "another.one":{ 74 | "subscribe":{ 75 | "message":{"$ref":"#/components/messages/Asyncapi240TestMyAnotherMessage"} 76 | } 77 | }, 78 | "one.{name}.two":{ 79 | "parameters":{"name":{"schema":{"description":"Name","type":"string"}}}, 80 | "publish":{"message":{"$ref":"#/components/messages/Asyncapi240TestMyMessage"}} 81 | } 82 | }, 83 | "components":{ 84 | "schemas":{ 85 | "Asyncapi240TestMyAnotherMessage":{ 86 | "properties":{ 87 | "item":{ 88 | "$ref":"#/components/schemas/Asyncapi240TestSubItem", 89 | "description":"Some item" 90 | } 91 | }, 92 | "type":"object" 93 | }, 94 | "Asyncapi240TestMyMessage":{ 95 | "properties":{ 96 | "createdAt":{"description":"Creation time","format":"date-time","type":"string"}, 97 | "items":{ 98 | "description":"List of items", 99 | "items":{"$ref":"#/components/schemas/Asyncapi240TestSubItem"}, 100 | "type":["array","null"] 101 | } 102 | }, 103 | "type":"object" 104 | }, 105 | "Asyncapi240TestSubItem":{ 106 | "properties":{ 107 | "key":{"description":"Item key","type":"string"}, 108 | "values":{ 109 | "description":"List of item values","items":{"type":"integer"}, 110 | "type":["array","null"],"uniqueItems":true 111 | } 112 | }, 113 | "type":"object" 114 | } 115 | }, 116 | "messages":{ 117 | "Asyncapi240TestMyAnotherMessage":{ 118 | "headers":{ 119 | "properties":{"X-Trace-ID":{"description":"Tracing header","type":"string"}}, 120 | "required":["X-Trace-ID"],"type":"object" 121 | }, 122 | "payload":{"$ref":"#/components/schemas/Asyncapi240TestMyAnotherMessage"}, 123 | "summary":"Sample consumer", 124 | "description":"This is another sample schema" 125 | }, 126 | "Asyncapi240TestMyMessage":{ 127 | "payload":{"$ref":"#/components/schemas/Asyncapi240TestMyMessage"}, 128 | "summary":"Sample publisher","description":"This is a sample schema" 129 | } 130 | } 131 | } 132 | }`), r.Schema) 133 | } 134 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.4.0/sample-amqp.yaml: -------------------------------------------------------------------------------- 1 | asyncapi: 2.4.0 2 | info: 3 | title: My Lovely Messaging API 4 | version: 1.2.3 5 | servers: 6 | live: 7 | url: api.{country}.lovely.com:5672 8 | description: Production instance. 9 | protocol: amqp 10 | protocolVersion: 0.9.1 11 | variables: 12 | country: 13 | enum: 14 | - RU 15 | - US 16 | - DE 17 | - FR 18 | default: US 19 | description: Country code. 20 | channels: 21 | another.one: 22 | subscribe: 23 | message: 24 | $ref: '#/components/messages/Asyncapi240TestMyAnotherMessage' 25 | one.{name}.two: 26 | parameters: 27 | name: 28 | schema: 29 | description: Name 30 | type: string 31 | publish: 32 | message: 33 | $ref: '#/components/messages/Asyncapi240TestMyMessage' 34 | bindings: 35 | amqp: 36 | bindingVersion: 0.2.0 37 | is: routingKey 38 | exchange: 39 | name: some-exchange 40 | components: 41 | schemas: 42 | Asyncapi240TestMyAnotherMessage: 43 | properties: 44 | item: 45 | $ref: '#/components/schemas/Asyncapi240TestSubItem' 46 | description: Some item 47 | type: object 48 | Asyncapi240TestMyMessage: 49 | properties: 50 | createdAt: 51 | description: Creation time 52 | format: date-time 53 | type: string 54 | items: 55 | description: List of items 56 | items: 57 | $ref: '#/components/schemas/Asyncapi240TestSubItem' 58 | type: 59 | - array 60 | - "null" 61 | type: object 62 | Asyncapi240TestSubItem: 63 | properties: 64 | key: 65 | description: Item key 66 | type: string 67 | values: 68 | description: List of item values 69 | items: 70 | type: integer 71 | type: 72 | - array 73 | - "null" 74 | uniqueItems: true 75 | type: object 76 | messages: 77 | Asyncapi240TestMyAnotherMessage: 78 | headers: 79 | properties: 80 | X-Trace-ID: 81 | description: Tracing header 82 | type: string 83 | required: 84 | - X-Trace-ID 85 | type: object 86 | payload: 87 | $ref: '#/components/schemas/Asyncapi240TestMyAnotherMessage' 88 | summary: Sample consumer 89 | description: This is another sample schema. 90 | Asyncapi240TestMyMessage: 91 | payload: 92 | $ref: '#/components/schemas/Asyncapi240TestMyMessage' 93 | summary: Sample publisher 94 | description: This is a sample schema. 95 | -------------------------------------------------------------------------------- /reflector/asyncapi-2.4.0/sample-kafka.yaml: -------------------------------------------------------------------------------- 1 | asyncapi: 2.4.0 2 | info: 3 | title: My Lovely Messaging API 4 | version: 1.2.3 5 | servers: 6 | live: 7 | url: api.{country}.lovely.com:5672 8 | description: Production instance. 9 | protocol: kafka 10 | protocolVersion: 1.0.0 11 | variables: 12 | country: 13 | enum: 14 | - RU 15 | - US 16 | - DE 17 | - FR 18 | default: US 19 | description: Country code. 20 | channels: 21 | another.one: 22 | subscribe: 23 | bindings: 24 | kafka: 25 | bindingVersion: 0.3.0 26 | groupId: my-group-id-2 27 | clientId: my-client-id 28 | message: 29 | $ref: '#/components/messages/Asyncapi240TestMyAnotherMessage' 30 | one.{name}.two: 31 | parameters: 32 | name: 33 | schema: 34 | description: Name 35 | type: string 36 | publish: 37 | bindings: 38 | kafka: 39 | bindingVersion: 0.3.0 40 | groupId: my-group-id 41 | clientId: my-client-id 42 | message: 43 | $ref: '#/components/messages/Asyncapi240TestMyMessage' 44 | components: 45 | schemas: 46 | Asyncapi240TestMyAnotherMessage: 47 | properties: 48 | item: 49 | $ref: '#/components/schemas/Asyncapi240TestSubItem' 50 | description: Some item 51 | type: object 52 | Asyncapi240TestMyMessage: 53 | properties: 54 | createdAt: 55 | description: Creation time 56 | format: date-time 57 | type: string 58 | items: 59 | description: List of items 60 | items: 61 | $ref: '#/components/schemas/Asyncapi240TestSubItem' 62 | type: 63 | - array 64 | - "null" 65 | type: object 66 | Asyncapi240TestSubItem: 67 | properties: 68 | key: 69 | description: Item key 70 | type: string 71 | values: 72 | description: List of item values 73 | items: 74 | type: integer 75 | type: 76 | - array 77 | - "null" 78 | uniqueItems: true 79 | type: object 80 | messages: 81 | Asyncapi240TestMyAnotherMessage: 82 | headers: 83 | properties: 84 | X-Trace-ID: 85 | description: Tracing header 86 | type: string 87 | required: 88 | - X-Trace-ID 89 | type: object 90 | payload: 91 | $ref: '#/components/schemas/Asyncapi240TestMyAnotherMessage' 92 | summary: Sample consumer 93 | description: This is another sample schema. 94 | Asyncapi240TestMyMessage: 95 | payload: 96 | $ref: '#/components/schemas/Asyncapi240TestMyMessage' 97 | summary: Sample publisher 98 | description: This is a sample schema. 99 | bindings: 100 | kafka: 101 | bindingVersion: 0.3.0 102 | key: my-key 103 | -------------------------------------------------------------------------------- /resources/fixtures/streetlights-2.0.0.json: -------------------------------------------------------------------------------- 1 | { 2 | "asyncapi": "2.0.0", 3 | "info": { 4 | "title": "Streetlights API", 5 | "version": "1.0.0", 6 | "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 \ud83c\udf03\n* Dim a specific streetlight \ud83d\ude0e\n* Receive real-time information about environmental lighting conditions \ud83d\udcc8\n", 7 | "license": { 8 | "name": "Apache 2.0", 9 | "url": "https://www.apache.org/licenses/LICENSE-2.0" 10 | } 11 | }, 12 | "servers": { 13 | "production": { 14 | "url": "test.mosquitto.org:{port}", 15 | "protocol": "mqtt", 16 | "description": "Test broker", 17 | "variables": { 18 | "port": { 19 | "description": "Secure connection (TLS) is available through port 8883.", 20 | "default": "1883", 21 | "enum": [ 22 | "1883", 23 | "8883" 24 | ] 25 | } 26 | }, 27 | "security": [ 28 | { 29 | "apiKey": [] 30 | }, 31 | { 32 | "supportedOauthFlows": [ 33 | "streetlights:on", 34 | "streetlights:off", 35 | "streetlights:dim" 36 | ] 37 | }, 38 | { 39 | "openIdConnectWellKnown": [] 40 | } 41 | ] 42 | } 43 | }, 44 | "defaultContentType": "application/json", 45 | "channels": { 46 | "smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured": { 47 | "description": "The topic on which measured values may be produced and consumed.", 48 | "parameters": { 49 | "streetlightId": { 50 | "$ref": "#/components/parameters/streetlightId" 51 | } 52 | }, 53 | "publish": { 54 | "summary": "Inform about environmental lighting conditions of a particular streetlight.", 55 | "operationId": "receiveLightMeasurement", 56 | "traits": [ 57 | { 58 | "$ref": "#/components/operationTraits/kafka" 59 | } 60 | ], 61 | "message": { 62 | "$ref": "#/components/messages/lightMeasured" 63 | } 64 | } 65 | }, 66 | "smartylighting/streetlights/1/0/action/{streetlightId}/turn/on": { 67 | "parameters": { 68 | "streetlightId": { 69 | "$ref": "#/components/parameters/streetlightId" 70 | } 71 | }, 72 | "subscribe": { 73 | "operationId": "turnOn", 74 | "traits": [ 75 | { 76 | "$ref": "#/components/operationTraits/kafka" 77 | } 78 | ], 79 | "message": { 80 | "$ref": "#/components/messages/turnOnOff" 81 | } 82 | } 83 | }, 84 | "smartylighting/streetlights/1/0/action/{streetlightId}/turn/off": { 85 | "parameters": { 86 | "streetlightId": { 87 | "$ref": "#/components/parameters/streetlightId" 88 | } 89 | }, 90 | "subscribe": { 91 | "operationId": "turnOff", 92 | "traits": [ 93 | { 94 | "$ref": "#/components/operationTraits/kafka" 95 | } 96 | ], 97 | "message": { 98 | "$ref": "#/components/messages/turnOnOff" 99 | } 100 | } 101 | }, 102 | "smartylighting/streetlights/1/0/action/{streetlightId}/dim": { 103 | "parameters": { 104 | "streetlightId": { 105 | "$ref": "#/components/parameters/streetlightId" 106 | } 107 | }, 108 | "subscribe": { 109 | "operationId": "dimLight", 110 | "traits": [ 111 | { 112 | "$ref": "#/components/operationTraits/kafka" 113 | } 114 | ], 115 | "message": { 116 | "$ref": "#/components/messages/dimLight" 117 | } 118 | } 119 | } 120 | }, 121 | "components": { 122 | "messages": { 123 | "lightMeasured": { 124 | "name": "lightMeasured", 125 | "title": "Light measured", 126 | "summary": "Inform about environmental lighting conditions of a particular streetlight.", 127 | "contentType": "application/json", 128 | "traits": [ 129 | { 130 | "$ref": "#/components/messageTraits/commonHeaders" 131 | } 132 | ], 133 | "payload": { 134 | "$ref": "#/components/schemas/lightMeasuredPayload" 135 | } 136 | }, 137 | "turnOnOff": { 138 | "name": "turnOnOff", 139 | "title": "Turn on/off", 140 | "summary": "Command a particular streetlight to turn the lights on or off.", 141 | "traits": [ 142 | { 143 | "$ref": "#/components/messageTraits/commonHeaders" 144 | } 145 | ], 146 | "payload": { 147 | "$ref": "#/components/schemas/turnOnOffPayload" 148 | } 149 | }, 150 | "dimLight": { 151 | "name": "dimLight", 152 | "title": "Dim light", 153 | "summary": "Command a particular streetlight to dim the lights.", 154 | "traits": [ 155 | { 156 | "$ref": "#/components/messageTraits/commonHeaders" 157 | } 158 | ], 159 | "payload": { 160 | "$ref": "#/components/schemas/dimLightPayload" 161 | } 162 | } 163 | }, 164 | "schemas": { 165 | "lightMeasuredPayload": { 166 | "type": "object", 167 | "properties": { 168 | "lumens": { 169 | "type": "integer", 170 | "minimum": 0, 171 | "description": "Light intensity measured in lumens." 172 | }, 173 | "sentAt": { 174 | "$ref": "#/components/schemas/sentAt" 175 | } 176 | } 177 | }, 178 | "turnOnOffPayload": { 179 | "type": "object", 180 | "properties": { 181 | "command": { 182 | "type": "string", 183 | "enum": [ 184 | "on", 185 | "off" 186 | ], 187 | "description": "Whether to turn on or off the light." 188 | }, 189 | "sentAt": { 190 | "$ref": "#/components/schemas/sentAt" 191 | } 192 | } 193 | }, 194 | "dimLightPayload": { 195 | "type": "object", 196 | "properties": { 197 | "percentage": { 198 | "type": "integer", 199 | "description": "Percentage to which the light should be dimmed to.", 200 | "minimum": 0, 201 | "maximum": 100 202 | }, 203 | "sentAt": { 204 | "$ref": "#/components/schemas/sentAt" 205 | } 206 | } 207 | }, 208 | "sentAt": { 209 | "type": "string", 210 | "format": "date-time", 211 | "description": "Date and time when the message was sent." 212 | } 213 | }, 214 | "securitySchemes": { 215 | "apiKey": { 216 | "type": "apiKey", 217 | "in": "user", 218 | "description": "Provide your API key as the user and leave the password empty." 219 | }, 220 | "supportedOauthFlows": { 221 | "type": "oauth2", 222 | "description": "Flows to support OAuth 2.0", 223 | "flows": { 224 | "implicit": { 225 | "authorizationUrl": "https://authserver.example/auth", 226 | "scopes": { 227 | "streetlights:on": "Ability to switch lights on", 228 | "streetlights:off": "Ability to switch lights off", 229 | "streetlights:dim": "Ability to dim the lights" 230 | } 231 | }, 232 | "password": { 233 | "tokenUrl": "https://authserver.example/token", 234 | "scopes": { 235 | "streetlights:on": "Ability to switch lights on", 236 | "streetlights:off": "Ability to switch lights off", 237 | "streetlights:dim": "Ability to dim the lights" 238 | } 239 | }, 240 | "clientCredentials": { 241 | "tokenUrl": "https://authserver.example/token", 242 | "scopes": { 243 | "streetlights:on": "Ability to switch lights on", 244 | "streetlights:off": "Ability to switch lights off", 245 | "streetlights:dim": "Ability to dim the lights" 246 | } 247 | }, 248 | "authorizationCode": { 249 | "authorizationUrl": "https://authserver.example/auth", 250 | "tokenUrl": "https://authserver.example/token", 251 | "refreshUrl": "https://authserver.example/refresh", 252 | "scopes": { 253 | "streetlights:on": "Ability to switch lights on", 254 | "streetlights:off": "Ability to switch lights off", 255 | "streetlights:dim": "Ability to dim the lights" 256 | } 257 | } 258 | } 259 | }, 260 | "openIdConnectWellKnown": { 261 | "type": "openIdConnect", 262 | "openIdConnectUrl": "https://authserver.example/.well-known" 263 | } 264 | }, 265 | "parameters": { 266 | "streetlightId": { 267 | "description": "The ID of the streetlight.", 268 | "schema": { 269 | "type": "string" 270 | } 271 | } 272 | }, 273 | "messageTraits": { 274 | "commonHeaders": { 275 | "headers": { 276 | "type": "object", 277 | "properties": { 278 | "my-app-header": { 279 | "type": "integer", 280 | "minimum": 0, 281 | "maximum": 100 282 | } 283 | } 284 | } 285 | } 286 | }, 287 | "operationTraits": { 288 | "kafka": { 289 | "bindings": { 290 | "kafka": { 291 | "clientId": "my-app-id" 292 | } 293 | } 294 | } 295 | } 296 | } 297 | } -------------------------------------------------------------------------------- /resources/fixtures/streetlights-2.0.0.yml: -------------------------------------------------------------------------------- 1 | asyncapi: 2.0.0 2 | info: 3 | title: Streetlights API 4 | version: 1.0.0 5 | description: "The Smartylighting Streetlights API allows you to remotely manage 6 | the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight 7 | on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time 8 | information about environmental lighting conditions \U0001F4C8\n" 9 | license: 10 | name: Apache 2.0 11 | url: https://www.apache.org/licenses/LICENSE-2.0 12 | servers: 13 | production: 14 | url: test.mosquitto.org:{port} 15 | description: Test broker 16 | protocol: mqtt 17 | variables: 18 | port: 19 | enum: 20 | - "1883" 21 | - "8883" 22 | default: "1883" 23 | description: Secure connection (TLS) is available through port 8883. 24 | security: 25 | - apiKey: [] 26 | - supportedOauthFlows: 27 | - streetlights:on 28 | - streetlights:off 29 | - streetlights:dim 30 | - openIdConnectWellKnown: [] 31 | defaultContentType: application/json 32 | channels: 33 | smartylighting/streetlights/1/0/action/{streetlightId}/dim: 34 | parameters: 35 | streetlightId: 36 | $ref: '#/components/parameters/streetlightId' 37 | subscribe: 38 | traits: 39 | - $ref: '#/components/operationTraits/kafka' 40 | operationId: dimLight 41 | message: 42 | $ref: '#/components/messages/dimLight' 43 | smartylighting/streetlights/1/0/action/{streetlightId}/turn/off: 44 | parameters: 45 | streetlightId: 46 | $ref: '#/components/parameters/streetlightId' 47 | subscribe: 48 | traits: 49 | - $ref: '#/components/operationTraits/kafka' 50 | operationId: turnOff 51 | message: 52 | $ref: '#/components/messages/turnOnOff' 53 | smartylighting/streetlights/1/0/action/{streetlightId}/turn/on: 54 | parameters: 55 | streetlightId: 56 | $ref: '#/components/parameters/streetlightId' 57 | subscribe: 58 | traits: 59 | - $ref: '#/components/operationTraits/kafka' 60 | operationId: turnOn 61 | message: 62 | $ref: '#/components/messages/turnOnOff' 63 | smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured: 64 | parameters: 65 | streetlightId: 66 | $ref: '#/components/parameters/streetlightId' 67 | description: The topic on which measured values may be produced and consumed. 68 | publish: 69 | traits: 70 | - $ref: '#/components/operationTraits/kafka' 71 | summary: Inform about environmental lighting conditions of a particular streetlight. 72 | operationId: receiveLightMeasurement 73 | message: 74 | $ref: '#/components/messages/lightMeasured' 75 | components: 76 | schemas: 77 | dimLightPayload: 78 | properties: 79 | percentage: 80 | description: Percentage to which the light should be dimmed to. 81 | maximum: 100 82 | minimum: 0 83 | type: integer 84 | sentAt: 85 | $ref: '#/components/schemas/sentAt' 86 | type: object 87 | lightMeasuredPayload: 88 | properties: 89 | lumens: 90 | description: Light intensity measured in lumens. 91 | minimum: 0 92 | type: integer 93 | sentAt: 94 | $ref: '#/components/schemas/sentAt' 95 | type: object 96 | sentAt: 97 | description: Date and time when the message was sent. 98 | format: date-time 99 | type: string 100 | turnOnOffPayload: 101 | properties: 102 | command: 103 | description: Whether to turn on or off the light. 104 | enum: 105 | - true 106 | - false 107 | type: string 108 | sentAt: 109 | $ref: '#/components/schemas/sentAt' 110 | type: object 111 | messages: 112 | dimLight: 113 | payload: 114 | $ref: '#/components/schemas/dimLightPayload' 115 | summary: Command a particular streetlight to dim the lights. 116 | name: dimLight 117 | title: Dim light 118 | traits: 119 | - $ref: '#/components/messageTraits/commonHeaders' 120 | lightMeasured: 121 | contentType: application/json 122 | payload: 123 | $ref: '#/components/schemas/lightMeasuredPayload' 124 | summary: Inform about environmental lighting conditions of a particular streetlight. 125 | name: lightMeasured 126 | title: Light measured 127 | traits: 128 | - $ref: '#/components/messageTraits/commonHeaders' 129 | turnOnOff: 130 | payload: 131 | $ref: '#/components/schemas/turnOnOffPayload' 132 | summary: Command a particular streetlight to turn the lights on or off. 133 | name: turnOnOff 134 | title: Turn on/off 135 | traits: 136 | - $ref: '#/components/messageTraits/commonHeaders' 137 | securitySchemes: 138 | apiKey: 139 | type: apiKey 140 | in: user 141 | description: Provide your API key as the user and leave the password empty. 142 | openIdConnectWellKnown: 143 | type: openIdConnect 144 | openIdConnectUrl: https://authserver.example/.well-known 145 | supportedOauthFlows: 146 | type: oauth2 147 | description: Flows to support OAuth 2.0 148 | flows: 149 | implicit: 150 | authorizationUrl: https://authserver.example/auth 151 | scopes: 152 | streetlights:dim: Ability to dim the lights 153 | streetlights:off: Ability to switch lights off 154 | streetlights:on: Ability to switch lights on 155 | password: 156 | tokenUrl: https://authserver.example/token 157 | scopes: 158 | streetlights:dim: Ability to dim the lights 159 | streetlights:off: Ability to switch lights off 160 | streetlights:on: Ability to switch lights on 161 | clientCredentials: 162 | tokenUrl: https://authserver.example/token 163 | scopes: 164 | streetlights:dim: Ability to dim the lights 165 | streetlights:off: Ability to switch lights off 166 | streetlights:on: Ability to switch lights on 167 | authorizationCode: 168 | authorizationUrl: https://authserver.example/auth 169 | tokenUrl: https://authserver.example/token 170 | refreshUrl: https://authserver.example/refresh 171 | scopes: 172 | streetlights:dim: Ability to dim the lights 173 | streetlights:off: Ability to switch lights off 174 | streetlights:on: Ability to switch lights on 175 | parameters: 176 | streetlightId: 177 | description: The ID of the streetlight. 178 | schema: 179 | type: string 180 | operationTraits: 181 | kafka: 182 | bindings: 183 | kafka: 184 | clientId: my-app-id 185 | messageTraits: 186 | commonHeaders: 187 | headers: 188 | properties: 189 | my-app-header: 190 | maximum: 100 191 | minimum: 0 192 | type: integer 193 | type: object 194 | -------------------------------------------------------------------------------- /resources/fixtures/streetlights-2.1.0-kafka.json: -------------------------------------------------------------------------------- 1 | { 2 | "asyncapi": "2.1.0", 3 | "info": { 4 | "title": "Streetlights Kafka API", 5 | "version": "1.0.0", 6 | "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 🌃\n* Dim a specific streetlight 😎\n* Receive real-time information about environmental lighting conditions 📈\n", 7 | "license": { 8 | "name": "Apache 2.0", 9 | "url": "https://www.apache.org/licenses/LICENSE-2.0" 10 | } 11 | }, 12 | "servers": { 13 | "test": { 14 | "url": "test.mykafkacluster.org:8092", 15 | "protocol": "kafka-secure", 16 | "description": "Test broker", 17 | "security": [ 18 | { 19 | "saslScram": [] 20 | } 21 | ] 22 | } 23 | }, 24 | "defaultContentType": "application/json", 25 | "channels": { 26 | "smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured": { 27 | "description": "The topic on which measured values may be produced and consumed.", 28 | "parameters": { 29 | "streetlightId": { 30 | "$ref": "#/components/parameters/streetlightId" 31 | } 32 | }, 33 | "publish": { 34 | "summary": "Inform about environmental lighting conditions of a particular streetlight.", 35 | "operationId": "receiveLightMeasurement", 36 | "traits": [ 37 | { 38 | "$ref": "#/components/operationTraits/kafka" 39 | } 40 | ], 41 | "message": { 42 | "$ref": "#/components/messages/lightMeasured" 43 | } 44 | } 45 | }, 46 | "smartylighting.streetlights.1.0.action.{streetlightId}.turn.on": { 47 | "parameters": { 48 | "streetlightId": { 49 | "$ref": "#/components/parameters/streetlightId" 50 | } 51 | }, 52 | "subscribe": { 53 | "operationId": "turnOn", 54 | "traits": [ 55 | { 56 | "$ref": "#/components/operationTraits/kafka" 57 | } 58 | ], 59 | "message": { 60 | "$ref": "#/components/messages/turnOnOff" 61 | } 62 | } 63 | }, 64 | "smartylighting.streetlights.1.0.action.{streetlightId}.turn.off": { 65 | "parameters": { 66 | "streetlightId": { 67 | "$ref": "#/components/parameters/streetlightId" 68 | } 69 | }, 70 | "subscribe": { 71 | "operationId": "turnOff", 72 | "traits": [ 73 | { 74 | "$ref": "#/components/operationTraits/kafka" 75 | } 76 | ], 77 | "message": { 78 | "$ref": "#/components/messages/turnOnOff" 79 | } 80 | } 81 | }, 82 | "smartylighting.streetlights.1.0.action.{streetlightId}.dim": { 83 | "parameters": { 84 | "streetlightId": { 85 | "$ref": "#/components/parameters/streetlightId" 86 | } 87 | }, 88 | "subscribe": { 89 | "operationId": "dimLight", 90 | "traits": [ 91 | { 92 | "$ref": "#/components/operationTraits/kafka" 93 | } 94 | ], 95 | "message": { 96 | "$ref": "#/components/messages/dimLight" 97 | } 98 | } 99 | } 100 | }, 101 | "components": { 102 | "messages": { 103 | "lightMeasured": { 104 | "name": "lightMeasured", 105 | "title": "Light measured", 106 | "summary": "Inform about environmental lighting conditions of a particular streetlight.", 107 | "contentType": "application/json", 108 | "traits": [ 109 | { 110 | "$ref": "#/components/messageTraits/commonHeaders" 111 | } 112 | ], 113 | "payload": { 114 | "$ref": "#/components/schemas/lightMeasuredPayload" 115 | } 116 | }, 117 | "turnOnOff": { 118 | "name": "turnOnOff", 119 | "title": "Turn on/off", 120 | "summary": "Command a particular streetlight to turn the lights on or off.", 121 | "traits": [ 122 | { 123 | "$ref": "#/components/messageTraits/commonHeaders" 124 | } 125 | ], 126 | "payload": { 127 | "$ref": "#/components/schemas/turnOnOffPayload" 128 | } 129 | }, 130 | "dimLight": { 131 | "name": "dimLight", 132 | "title": "Dim light", 133 | "summary": "Command a particular streetlight to dim the lights.", 134 | "traits": [ 135 | { 136 | "$ref": "#/components/messageTraits/commonHeaders" 137 | } 138 | ], 139 | "payload": { 140 | "$ref": "#/components/schemas/dimLightPayload" 141 | } 142 | } 143 | }, 144 | "schemas": { 145 | "lightMeasuredPayload": { 146 | "type": "object", 147 | "properties": { 148 | "lumens": { 149 | "type": "integer", 150 | "minimum": 0, 151 | "description": "Light intensity measured in lumens." 152 | }, 153 | "sentAt": { 154 | "$ref": "#/components/schemas/sentAt" 155 | } 156 | } 157 | }, 158 | "turnOnOffPayload": { 159 | "type": "object", 160 | "properties": { 161 | "command": { 162 | "type": "string", 163 | "enum": [ 164 | "on", 165 | "off" 166 | ], 167 | "description": "Whether to turn on or off the light." 168 | }, 169 | "sentAt": { 170 | "$ref": "#/components/schemas/sentAt" 171 | } 172 | } 173 | }, 174 | "dimLightPayload": { 175 | "type": "object", 176 | "properties": { 177 | "percentage": { 178 | "type": "integer", 179 | "description": "Percentage to which the light should be dimmed to.", 180 | "minimum": 0, 181 | "maximum": 100 182 | }, 183 | "sentAt": { 184 | "$ref": "#/components/schemas/sentAt" 185 | } 186 | } 187 | }, 188 | "sentAt": { 189 | "type": "string", 190 | "format": "date-time", 191 | "description": "Date and time when the message was sent." 192 | } 193 | }, 194 | "securitySchemes": { 195 | "saslScram": { 196 | "type": "scramSha256", 197 | "description": "Provide your username and password for SASL/SCRAM authentication" 198 | } 199 | }, 200 | "parameters": { 201 | "streetlightId": { 202 | "description": "The ID of the streetlight.", 203 | "schema": { 204 | "type": "string" 205 | } 206 | } 207 | }, 208 | "messageTraits": { 209 | "commonHeaders": { 210 | "headers": { 211 | "type": "object", 212 | "properties": { 213 | "my-app-header": { 214 | "type": "integer", 215 | "minimum": 0, 216 | "maximum": 100 217 | } 218 | } 219 | } 220 | } 221 | }, 222 | "operationTraits": { 223 | "kafka": { 224 | "bindings": { 225 | "kafka": { 226 | "clientId": "my-app-id" 227 | } 228 | } 229 | } 230 | } 231 | } 232 | } -------------------------------------------------------------------------------- /resources/fixtures/streetlights-2.1.0-kafka.yml: -------------------------------------------------------------------------------- 1 | asyncapi: 2.1.0 2 | info: 3 | title: Streetlights Kafka API 4 | version: 1.0.0 5 | description: "The Smartylighting Streetlights API allows you to remotely manage 6 | the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight 7 | on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time 8 | information about environmental lighting conditions \U0001F4C8\n" 9 | license: 10 | name: Apache 2.0 11 | url: https://www.apache.org/licenses/LICENSE-2.0 12 | servers: 13 | test: 14 | url: test.mykafkacluster.org:8092 15 | description: Test broker 16 | protocol: kafka-secure 17 | security: 18 | - saslScram: [] 19 | defaultContentType: application/json 20 | channels: 21 | smartylighting.streetlights.1.0.action.{streetlightId}.dim: 22 | parameters: 23 | streetlightId: 24 | $ref: '#/components/parameters/streetlightId' 25 | subscribe: 26 | traits: 27 | - $ref: '#/components/operationTraits/kafka' 28 | operationId: dimLight 29 | message: 30 | $ref: '#/components/messages/dimLight' 31 | smartylighting.streetlights.1.0.action.{streetlightId}.turn.off: 32 | parameters: 33 | streetlightId: 34 | $ref: '#/components/parameters/streetlightId' 35 | subscribe: 36 | traits: 37 | - $ref: '#/components/operationTraits/kafka' 38 | operationId: turnOff 39 | message: 40 | $ref: '#/components/messages/turnOnOff' 41 | smartylighting.streetlights.1.0.action.{streetlightId}.turn.on: 42 | parameters: 43 | streetlightId: 44 | $ref: '#/components/parameters/streetlightId' 45 | subscribe: 46 | traits: 47 | - $ref: '#/components/operationTraits/kafka' 48 | operationId: turnOn 49 | message: 50 | $ref: '#/components/messages/turnOnOff' 51 | smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured: 52 | parameters: 53 | streetlightId: 54 | $ref: '#/components/parameters/streetlightId' 55 | description: The topic on which measured values may be produced and consumed. 56 | publish: 57 | traits: 58 | - $ref: '#/components/operationTraits/kafka' 59 | summary: Inform about environmental lighting conditions of a particular streetlight. 60 | operationId: receiveLightMeasurement 61 | message: 62 | $ref: '#/components/messages/lightMeasured' 63 | components: 64 | schemas: 65 | dimLightPayload: 66 | properties: 67 | percentage: 68 | description: Percentage to which the light should be dimmed to. 69 | maximum: 100 70 | minimum: 0 71 | type: integer 72 | sentAt: 73 | $ref: '#/components/schemas/sentAt' 74 | type: object 75 | lightMeasuredPayload: 76 | properties: 77 | lumens: 78 | description: Light intensity measured in lumens. 79 | minimum: 0 80 | type: integer 81 | sentAt: 82 | $ref: '#/components/schemas/sentAt' 83 | type: object 84 | sentAt: 85 | description: Date and time when the message was sent. 86 | format: date-time 87 | type: string 88 | turnOnOffPayload: 89 | properties: 90 | command: 91 | description: Whether to turn on or off the light. 92 | enum: 93 | - true 94 | - false 95 | type: string 96 | sentAt: 97 | $ref: '#/components/schemas/sentAt' 98 | type: object 99 | messages: 100 | dimLight: 101 | payload: 102 | $ref: '#/components/schemas/dimLightPayload' 103 | summary: Command a particular streetlight to dim the lights. 104 | name: dimLight 105 | title: Dim light 106 | traits: 107 | - $ref: '#/components/messageTraits/commonHeaders' 108 | lightMeasured: 109 | contentType: application/json 110 | payload: 111 | $ref: '#/components/schemas/lightMeasuredPayload' 112 | summary: Inform about environmental lighting conditions of a particular streetlight. 113 | name: lightMeasured 114 | title: Light measured 115 | traits: 116 | - $ref: '#/components/messageTraits/commonHeaders' 117 | turnOnOff: 118 | payload: 119 | $ref: '#/components/schemas/turnOnOffPayload' 120 | summary: Command a particular streetlight to turn the lights on or off. 121 | name: turnOnOff 122 | title: Turn on/off 123 | traits: 124 | - $ref: '#/components/messageTraits/commonHeaders' 125 | securitySchemes: 126 | saslScram: 127 | type: scramSha256 128 | description: Provide your username and password for SASL/SCRAM authentication 129 | parameters: 130 | streetlightId: 131 | description: The ID of the streetlight. 132 | schema: 133 | type: string 134 | operationTraits: 135 | kafka: 136 | bindings: 137 | kafka: 138 | clientId: my-app-id 139 | messageTraits: 140 | commonHeaders: 141 | headers: 142 | properties: 143 | my-app-header: 144 | maximum: 100 145 | minimum: 0 146 | type: integer 147 | type: object 148 | -------------------------------------------------------------------------------- /resources/schema/amqp-channel-binding-object-0.1.0.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "title": "AMQP 0-9-1 Channel Binding Object", 4 | "description": "This object contains information about the channel representation in AMQP.\nSee https://github.com/asyncapi/bindings/tree/master/amqp#channel-binding-object.", 5 | "type": "object", 6 | "properties": { 7 | "is": { 8 | "description": "Defines what type of channel is it. Can be either `queue` or `routingKey` (default).", 9 | "type": "string", 10 | "enum": [ 11 | "routingKey", 12 | "queue" 13 | ], 14 | "default": "routingKey" 15 | }, 16 | "exchange": { 17 | "$ref": "#/definitions/exchange" 18 | }, 19 | "queue": { 20 | "$ref": "#/definitions/queue" 21 | }, 22 | "bindingVersion": { 23 | "description": "The version of this binding. If omitted, \"latest\" MUST be assumed.", 24 | "type": "string", 25 | "enum": [ 26 | "0.1.0", 27 | "latest" 28 | ] 29 | } 30 | }, 31 | "allOf": [ 32 | { 33 | "description": "Queue and exchange are mutually exclusive.", 34 | "not": { 35 | "required": [ 36 | "queue", 37 | "exchange" 38 | ] 39 | } 40 | } 41 | ], 42 | "oneOf": [ 43 | { 44 | "properties": { 45 | "is": { 46 | "const": "queue" 47 | } 48 | }, 49 | "required": [ 50 | "queue" 51 | ] 52 | }, 53 | { 54 | "properties": { 55 | "is": { 56 | "const": "routingKey" 57 | } 58 | }, 59 | "required": [ 60 | "exchange" 61 | ] 62 | } 63 | ], 64 | "additionalProperties": false, 65 | "definitions": { 66 | "exchange": { 67 | "description": "When `is`=`routingKey`, this object defines the exchange properties.", 68 | "type": "object", 69 | "properties": { 70 | "name": { 71 | "description": "The name of the exchange. It MUST NOT exceed 255 characters long.", 72 | "type": "string", 73 | "maxLength": 255 74 | }, 75 | "type": { 76 | "description": "The type of the exchange. Can be either `topic`, `direct`, `fanout`, `default` or `headers`.", 77 | "type": "string", 78 | "enum": [ 79 | "topic", 80 | "direct", 81 | "fanout", 82 | "default", 83 | "headers" 84 | ] 85 | }, 86 | "durable": { 87 | "description": "Whether the exchange should survive broker restarts or not.", 88 | "type": "boolean" 89 | }, 90 | "autoDelete": { 91 | "description": "Whether the exchange should be deleted when the last queue is unbound from it.", 92 | "type": "boolean" 93 | }, 94 | "vhost": { 95 | "description": "The virtual host of the queue. Defaults to /.", 96 | "type": "string" 97 | } 98 | } 99 | }, 100 | "queue": { 101 | "description": "When `is`=`queue`, this object defines the queue properties.", 102 | "type": "object", 103 | "properties": { 104 | "name": { 105 | "description": "The name of the queue. It MUST NOT exceed 255 characters long.", 106 | "type": "string", 107 | "maxLength": 255 108 | }, 109 | "durable": { 110 | "description": "Whether the queue should survive broker restarts or not.", 111 | "type": "boolean" 112 | }, 113 | "exclusive": { 114 | "description": "Whether the queue should be used only by one connection or not.", 115 | "type": "boolean" 116 | }, 117 | "autoDelete": { 118 | "description": "Whether the queue should be deleted when the last consumer unsubscribes.", 119 | "type": "boolean" 120 | }, 121 | "vhost": { 122 | "description": "The virtual host of the queue. Defaults to /.", 123 | "type": "string" 124 | } 125 | } 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /resources/schema/amqp-message-binding-object-0.1.0.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "title": "AMQP 0-9-1 Message Binding Object", 4 | "description": "This object contains information about the message representation in AMQP.\nSee https://github.com/asyncapi/bindings/tree/master/amqp#message-binding-object.", 5 | "type": "object", 6 | "properties": { 7 | "contentEncoding": { 8 | "description": "A MIME encoding for the message content.", 9 | "type": "string" 10 | }, 11 | "messageType": { 12 | "description": "Application-specific message type.", 13 | "type": "string" 14 | }, 15 | "bindingVersion": { 16 | "description": "The version of this binding. If omitted, \"latest\" MUST be assumed.", 17 | "type": "string", 18 | "enum": [ 19 | "0.1.0", 20 | "latest" 21 | ] 22 | } 23 | }, 24 | "additionalProperties": false 25 | } -------------------------------------------------------------------------------- /resources/schema/amqp-operation-binding-object-0.1.0.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "title": "AMQP 0-9-1 Operation Binding Object", 4 | "description": "This object contains information about the operation representation in AMQP.\nSee https://github.com/asyncapi/bindings/tree/master/amqp#operation-binding-object.", 5 | "type": "object", 6 | "properties": { 7 | "expiration": { 8 | "description": "TTL (Time-To-Live) for the message. It MUST be greater than or equal to zero. Applies to Publish, Subscribe.", 9 | "type": "integer", 10 | "minimum": 0 11 | }, 12 | "userId": { 13 | "description": "Identifies the user who has sent the message. Applies to Publish, Subscribe.", 14 | "type": "string" 15 | }, 16 | "cc": { 17 | "description": "The routing keys the message should be routed to at the time of publishing. Applies to Publish, Subscribe.", 18 | "type": "array", 19 | "items": { 20 | "type": "string" 21 | } 22 | }, 23 | "priority": { 24 | "description": "A priority for the message. Applies to Publish, Subscribe.", 25 | "type": "integer" 26 | }, 27 | "deliveryMode": { 28 | "description": "Delivery mode of the message. Its value MUST be either 1 (transient) or 2 (persistent). Applies to Publish, Subscribe.", 29 | "type": "integer", 30 | "oneOf": [ 31 | { 32 | "title": "Transient", 33 | "const": 1 34 | }, 35 | { 36 | "title": "Persistent", 37 | "const": 2 38 | } 39 | ] 40 | }, 41 | "mandatory": { 42 | "description": "Whether the message is mandatory or not. Applies to Publish.", 43 | "type": "boolean" 44 | }, 45 | "bcc": { 46 | "description": "Like cc but consumers will not receive this information. Applies to Publish.", 47 | "type": "array", 48 | "items": { 49 | "type": "string" 50 | } 51 | }, 52 | "replyTo": { 53 | "description": "Name of the queue where the consumer should send the response. Applies to Publish, Subscribe.", 54 | "type": "string" 55 | }, 56 | "timestamp": { 57 | "description": "Whether the message should include a timestamp or not. Applies to Publish, Subscribe.", 58 | "type": "boolean" 59 | }, 60 | "ack": { 61 | "description": "Whether the consumer should ack the message or not. Applies to Subscribe.", 62 | "type": "boolean" 63 | }, 64 | "bindingVersion": { 65 | "description": "The version of this binding. If omitted, \"latest\" MUST be assumed. Applies to Publish, Subscribe.", 66 | "type": "string", 67 | "enum": [ 68 | "0.1.0", 69 | "latest" 70 | ] 71 | } 72 | }, 73 | "additionalProperties": false 74 | } -------------------------------------------------------------------------------- /resources/schema/asyncapi-2.1.0-patch.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "value":"#/definitions/bindingsObject","op":"test", 4 | "path":"/definitions/server/properties/bindings/$ref" 5 | }, 6 | { 7 | "value":"#/definitions/serverBindingsObject","op":"replace", 8 | "path":"/definitions/server/properties/bindings/$ref" 9 | }, 10 | { 11 | "value":"#/definitions/bindingsObject","op":"test", 12 | "path":"/definitions/components/properties/serverBindings/additionalProperties/$ref" 13 | }, 14 | { 15 | "value":"#/definitions/serverBindingsObject","op":"replace", 16 | "path":"/definitions/components/properties/serverBindings/additionalProperties/$ref" 17 | }, 18 | { 19 | "value":"#/definitions/bindingsObject","op":"test", 20 | "path":"/definitions/components/properties/channelBindings/additionalProperties/$ref" 21 | }, 22 | { 23 | "value":"#/definitions/channelBindingsObject","op":"replace", 24 | "path":"/definitions/components/properties/channelBindings/additionalProperties/$ref" 25 | }, 26 | { 27 | "value":"#/definitions/bindingsObject","op":"test", 28 | "path":"/definitions/components/properties/operationBindings/additionalProperties/$ref" 29 | }, 30 | { 31 | "value":"#/definitions/operationBindingsObject","op":"replace", 32 | "path":"/definitions/components/properties/operationBindings/additionalProperties/$ref" 33 | }, 34 | { 35 | "value":"#/definitions/bindingsObject","op":"test", 36 | "path":"/definitions/components/properties/messageBindings/additionalProperties/$ref" 37 | }, 38 | { 39 | "value":"#/definitions/messageBindingsObject","op":"replace", 40 | "path":"/definitions/components/properties/messageBindings/additionalProperties/$ref" 41 | }, 42 | {"value":"map[string]interface{}","op":"add","path":"/definitions/schema/x-go-type"}, 43 | { 44 | "value":"#/definitions/bindingsObject","op":"test", 45 | "path":"/definitions/channelItem/properties/bindings/$ref" 46 | }, 47 | { 48 | "value":"#/definitions/channelBindingsObject","op":"replace", 49 | "path":"/definitions/channelItem/properties/bindings/$ref" 50 | }, 51 | { 52 | "value":"#/definitions/bindingsObject","op":"test", 53 | "path":"/definitions/operation/properties/bindings/$ref" 54 | }, 55 | { 56 | "value":"#/definitions/operationBindingsObject","op":"replace", 57 | "path":"/definitions/operation/properties/bindings/$ref" 58 | }, 59 | { 60 | "value":"#/definitions/schema","op":"add", 61 | "path":"/definitions/message/oneOf/1/oneOf/1/properties/payload/$ref" 62 | }, 63 | { 64 | "value":"#/definitions/bindingsObject","op":"test", 65 | "path":"/definitions/message/oneOf/1/oneOf/1/properties/bindings/$ref" 66 | }, 67 | { 68 | "value":"#/definitions/messageBindingsObject","op":"replace", 69 | "path":"/definitions/message/oneOf/1/oneOf/1/properties/bindings/$ref" 70 | }, 71 | {"value":"Message entity","op":"add","path":"/definitions/message/oneOf/1/oneOf/1/title"}, 72 | {"op":"remove","path":"/definitions/bindingsObject"}, 73 | { 74 | "value":"#/definitions/bindingsObject","op":"test", 75 | "path":"/definitions/operationTrait/properties/bindings/$ref" 76 | }, 77 | { 78 | "value":"#/definitions/operationBindingsObject","op":"replace", 79 | "path":"/definitions/operationTrait/properties/bindings/$ref" 80 | }, 81 | { 82 | "value":"#/definitions/bindingsObject","op":"test", 83 | "path":"/definitions/messageTrait/properties/bindings/$ref" 84 | }, 85 | { 86 | "value":"#/definitions/messageBindingsObject","op":"replace", 87 | "path":"/definitions/messageTrait/properties/bindings/$ref" 88 | }, 89 | { 90 | "value":{ 91 | "type":"object","additionalProperties":true, 92 | "properties":{ 93 | "http":{},"ws":{},"amqp":{"$ref":"amqp-channel-binding-object-0.1.0.json"},"amqp1":{},"mqtt":{}, 94 | "mqtt5":{},"kafka":{},"nats":{},"jms":{},"sns":{},"sqs":{},"stomp":{},"redis":{},"ibmmq":{} 95 | } 96 | }, 97 | "op":"add","path":"/definitions/channelBindingsObject" 98 | }, 99 | { 100 | "value":{ 101 | "type":"object","additionalProperties":true, 102 | "properties":{ 103 | "http":{},"ws":{},"amqp":{"$ref":"amqp-message-binding-object-0.1.0.json"},"amqp1":{},"mqtt":{}, 104 | "mqtt5":{},"kafka":{},"nats":{},"jms":{},"sns":{},"sqs":{},"stomp":{},"redis":{},"ibmmq":{} 105 | } 106 | }, 107 | "op":"add","path":"/definitions/messageBindingsObject" 108 | }, 109 | { 110 | "value":{ 111 | "type":"object","additionalProperties":true, 112 | "properties":{ 113 | "http":{},"ws":{},"amqp":{"$ref":"amqp-operation-binding-object-0.1.0.json"},"amqp1":{}, 114 | "mqtt":{},"mqtt5":{},"kafka":{},"nats":{},"jms":{},"sns":{},"sqs":{},"stomp":{},"redis":{}, 115 | "ibmmq":{} 116 | } 117 | }, 118 | "op":"add","path":"/definitions/operationBindingsObject" 119 | }, 120 | { 121 | "value":{ 122 | "type":"object","additionalProperties":true, 123 | "properties":{ 124 | "http":{},"ws":{},"amqp":{},"amqp1":{},"mqtt":{},"mqtt5":{},"kafka":{},"nats":{},"jms":{}, 125 | "sns":{},"sqs":{},"stomp":{},"redis":{},"ibmmq":{} 126 | } 127 | }, 128 | "op":"add","path":"/definitions/serverBindingsObject" 129 | } 130 | ] -------------------------------------------------------------------------------- /resources/schema/asyncapi-2.4.0-gen-cfg.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": { 3 | "/HTTPAsyncapiComDefinitions240([\\w\\d]+)JSON([\\w\\d]+)?/i": "${1}${2}", 4 | "/HTTPAsyncapiComBindings([\\w\\d]+)JSON/i": "${1}" 5 | }, 6 | "renames": { 7 | "HTTPAsyncapiComDefinitions240ServerJSON": "Server", 8 | "HTTPAsyncapiComDefinitions240MessageJSONOneOf1OneOf1": "MessageEntity", 9 | "HTTPAsyncapiComDefinitions240ServerVariableJSON": "ServerVariable", 10 | "HTTPAsyncapiComDefinitions240ChannelItemJSON": "ChannelItem" 11 | }, 12 | "validateRequired": true, 13 | "fluentSetters": true 14 | } -------------------------------------------------------------------------------- /resources/schema/asyncapi-2.4.0-patch.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "op":"test", 4 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1server.json/properties/bindings/$ref", 5 | "value":"http://asyncapi.com/definitions/2.4.0/bindingsObject.json" 6 | }, 7 | { 8 | "op":"replace", 9 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1server.json/properties/bindings/$ref", 10 | "value":"http://asyncapi.com/definitions/2.4.0/serverBindingsObject.json" 11 | }, 12 | { 13 | "op":"test", 14 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1channelItem.json/properties/bindings/$ref", 15 | "value":"http://asyncapi.com/definitions/2.4.0/bindingsObject.json" 16 | }, 17 | { 18 | "op":"replace", 19 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1channelItem.json/properties/bindings/$ref", 20 | "value":"http://asyncapi.com/definitions/2.4.0/channelBindingsObject.json" 21 | }, 22 | { 23 | "op":"remove", 24 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1parameter.json/properties/schema/$ref" 25 | }, 26 | { 27 | "op":"add", 28 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1parameter.json/properties/schema/x-go-type", 29 | "value":"map[string]interface{}" 30 | }, 31 | { 32 | "op":"add","path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1schema.json/x-go-type", 33 | "value":"map[string]interface{}" 34 | }, 35 | { 36 | "op":"test", 37 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1operation.json/properties/bindings/$ref", 38 | "value":"http://asyncapi.com/definitions/2.4.0/bindingsObject.json" 39 | }, 40 | { 41 | "op":"replace", 42 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1operation.json/properties/bindings/$ref", 43 | "value":"http://asyncapi.com/definitions/2.4.0/operationBindingsObject.json" 44 | }, 45 | { 46 | "op":"test", 47 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1operationTrait.json/properties/bindings/$ref", 48 | "value":"http://asyncapi.com/definitions/2.4.0/bindingsObject.json" 49 | }, 50 | { 51 | "op":"replace", 52 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1operationTrait.json/properties/bindings/$ref", 53 | "value":"http://asyncapi.com/definitions/2.4.0/operationBindingsObject.json" 54 | }, 55 | { 56 | "op":"add", 57 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1message.json/oneOf/1/oneOf/1/properties/payload/$ref", 58 | "value":"http://asyncapi.com/definitions/2.4.0/schema.json" 59 | }, 60 | { 61 | "op":"test", 62 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1message.json/oneOf/1/oneOf/1/properties/bindings/$ref", 63 | "value":"http://asyncapi.com/definitions/2.4.0/bindingsObject.json" 64 | }, 65 | { 66 | "op":"replace", 67 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1message.json/oneOf/1/oneOf/1/properties/bindings/$ref", 68 | "value":"http://asyncapi.com/definitions/2.4.0/messageBindingsObject.json" 69 | }, 70 | { 71 | "op":"test", 72 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1messageTrait.json/properties/bindings/$ref", 73 | "value":"http://asyncapi.com/definitions/2.4.0/bindingsObject.json" 74 | }, 75 | { 76 | "op":"replace", 77 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1messageTrait.json/properties/bindings/$ref", 78 | "value":"http://asyncapi.com/definitions/2.4.0/messageBindingsObject.json" 79 | }, 80 | { 81 | "op":"test", 82 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1components.json/properties/serverBindings/additionalProperties/$ref", 83 | "value":"http://asyncapi.com/definitions/2.4.0/bindingsObject.json" 84 | }, 85 | { 86 | "op":"replace", 87 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1components.json/properties/serverBindings/additionalProperties/$ref", 88 | "value":"http://asyncapi.com/definitions/2.4.0/serverBindingsObject.json" 89 | }, 90 | { 91 | "op":"test", 92 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1components.json/properties/channelBindings/additionalProperties/$ref", 93 | "value":"http://asyncapi.com/definitions/2.4.0/bindingsObject.json" 94 | }, 95 | { 96 | "op":"replace", 97 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1components.json/properties/channelBindings/additionalProperties/$ref", 98 | "value":"http://asyncapi.com/definitions/2.4.0/channelBindingsObject.json" 99 | }, 100 | { 101 | "op":"test", 102 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1components.json/properties/operationBindings/additionalProperties/$ref", 103 | "value":"http://asyncapi.com/definitions/2.4.0/bindingsObject.json" 104 | }, 105 | { 106 | "op":"replace", 107 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1components.json/properties/operationBindings/additionalProperties/$ref", 108 | "value":"http://asyncapi.com/definitions/2.4.0/operationBindingsObject.json" 109 | }, 110 | { 111 | "op":"test", 112 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1components.json/properties/messageBindings/additionalProperties/$ref", 113 | "value":"http://asyncapi.com/definitions/2.4.0/bindingsObject.json" 114 | }, 115 | { 116 | "op":"replace", 117 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1components.json/properties/messageBindings/additionalProperties/$ref", 118 | "value":"http://asyncapi.com/definitions/2.4.0/messageBindingsObject.json" 119 | }, 120 | { 121 | "op":"add", 122 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1operationBindingsObject.json", 123 | "value":{ 124 | "$id":"http://asyncapi.com/definitions/2.4.0/operationBindingsObject.json","type":"object", 125 | "additionalProperties":true, 126 | "properties":{ 127 | "http":{"$ref":"http://asyncapi.com/bindings/http/operation.json"},"ws":{}, 128 | "amqp":{"$ref":"http://asyncapi.com/bindings/amqp/operation.json"},"amqp1":{}, 129 | "mqtt":{"$ref":"http://asyncapi.com/bindings/mqtt/operation.json"},"mqtt5":{}, 130 | "kafka":{"$ref":"http://asyncapi.com/bindings/kafka/operation.json"},"anypointmq":{}, 131 | "nats":{"$ref":"http://asyncapi.com/bindings/nats/operation.json"},"jms":{},"sns":{},"sqs":{}, 132 | "stomp":{},"redis":{},"ibmmq":{}, 133 | "solace":{"$ref":"http://asyncapi.com/bindings/solace/operation.json"} 134 | } 135 | } 136 | }, 137 | { 138 | "op":"add", 139 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1messageBindingsObject.json", 140 | "value":{ 141 | "$id":"http://asyncapi.com/definitions/2.4.0/messageBindingsObject.json","type":"object", 142 | "additionalProperties":true, 143 | "properties":{ 144 | "http":{"$ref":"http://asyncapi.com/bindings/http/message.json"},"ws":{}, 145 | "amqp":{"$ref":"http://asyncapi.com/bindings/amqp/message.json"},"amqp1":{}, 146 | "mqtt":{"$ref":"http://asyncapi.com/bindings/mqtt/message.json"},"mqtt5":{}, 147 | "kafka":{"$ref":"http://asyncapi.com/bindings/kafka/message.json"}, 148 | "anypointmq":{"$ref":"http://asyncapi.com/bindings/anypointmq/message.json"},"nats":{},"jms":{}, 149 | "sns":{},"sqs":{},"stomp":{},"redis":{}, 150 | "ibmmq":{"$ref":"http://asyncapi.com/bindings/ibmmq/message.json"},"solace":{} 151 | } 152 | } 153 | }, 154 | { 155 | "op":"add", 156 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1channelBindingsObject.json", 157 | "value":{ 158 | "$id":"http://asyncapi.com/definitions/2.4.0/channelBindingsObject.json","type":"object", 159 | "additionalProperties":true, 160 | "properties":{ 161 | "http":{},"ws":{"$ref":"http://asyncapi.com/bindings/websockets/channel.json"}, 162 | "amqp":{"$ref":"http://asyncapi.com/bindings/amqp/channel.json"},"amqp1":{},"mqtt":{}, 163 | "mqtt5":{},"kafka":{}, 164 | "anypointmq":{"$ref":"http://asyncapi.com/bindings/anypointmq/channel.json"},"nats":{},"jms":{}, 165 | "sns":{},"sqs":{},"stomp":{},"redis":{}, 166 | "ibmmq":{"$ref":"http://asyncapi.com/bindings/ibmmq/channel.json"},"solace":{} 167 | } 168 | } 169 | }, 170 | { 171 | "op":"add", 172 | "path":"/definitions/http:~1~1asyncapi.com~1definitions~12.4.0~1serverBindingsObject.json", 173 | "value":{ 174 | "$id":"http://asyncapi.com/definitions/2.4.0/serverBindingsObject.json","type":"object", 175 | "additionalProperties":true, 176 | "properties":{ 177 | "http":{},"ws":{},"amqp":{},"amqp1":{}, 178 | "mqtt":{"$ref":"http://asyncapi.com/bindings/mqtt/server.json"},"mqtt5":{},"kafka":{}, 179 | "anypointmq":{},"nats":{},"jms":{},"sns":{},"sqs":{},"stomp":{},"redis":{}, 180 | "ibmmq":{"$ref":"http://asyncapi.com/bindings/ibmmq/server.json"}, 181 | "solace":{"$ref":"http://asyncapi.com/bindings/solace/server.json"} 182 | } 183 | } 184 | } 185 | ] -------------------------------------------------------------------------------- /resources/schema/bindings-resolver.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaFiles": { 3 | "http://asyncapi.com/bindings/amqp/channel.json": "bindings/amqp/json_schemas/channel.json", 4 | "http://asyncapi.com/bindings/amqp/message.json": "bindings/amqp/json_schemas/message.json", 5 | "http://asyncapi.com/bindings/amqp/operation.json": "bindings/amqp/json_schemas/operation.json", 6 | "http://asyncapi.com/bindings/anypointmq/channel.json": "bindings/anypointmq/json_schemas/channel.json", 7 | "http://asyncapi.com/bindings/anypointmq/message.json": "bindings/anypointmq/json_schemas/message.json", 8 | "http://asyncapi.com/bindings/http/message.json": "bindings/http/json_schemas/message.json", 9 | "http://asyncapi.com/bindings/http/operation.json": "bindings/http/json_schemas/operation.json", 10 | "http://asyncapi.com/bindings/ibmmq/channel.json": "bindings/ibmmq/json_schemas/channel.json", 11 | "http://asyncapi.com/bindings/ibmmq/message.json": "bindings/ibmmq/json_schemas/message.json", 12 | "http://asyncapi.com/bindings/ibmmq/server.json": "bindings/ibmmq/json_schemas/server.json", 13 | "http://asyncapi.com/bindings/kafka/message.json": "bindings/kafka/json_schemas/message.json", 14 | "http://asyncapi.com/bindings/kafka/operation.json": "bindings/kafka/json_schemas/operation.json", 15 | "http://asyncapi.com/bindings/mqtt/operation.json": "bindings/mqtt/json_schemas/operation.json", 16 | "http://asyncapi.com/bindings/mqtt/message.json": "bindings/mqtt/json_schemas/message.json", 17 | "http://asyncapi.com/bindings/mqtt/server.json": "bindings/mqtt/json_schemas/server.json", 18 | "http://asyncapi.com/bindings/nats/operation.json": "bindings/nats/json_schemas/operation.json", 19 | "http://asyncapi.com/bindings/solace/operation.json": "bindings/solace/json_schemas/operation.json", 20 | "http://asyncapi.com/bindings/solace/server.json": "bindings/solace/json_schemas/server.json", 21 | "http://asyncapi.com/bindings/websockets/channel.json": "bindings/websockets/json_schemas/channel.json" 22 | } 23 | } -------------------------------------------------------------------------------- /resources/schema/prepare_bindings.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | find ./bindings/ -name .git -prune -o -type f -not -name prepare_bindings.sh -print0 | xargs -0 perl -i -pe "s|https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/schema|http://asyncapi.com/definitions/2.4.0/schema.json|g" 4 | find ./bindings/ -name .git -prune -o -type f -not -name prepare_bindings.sh -print0 | xargs -0 perl -i -pe "s|https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/specificationExtension|http://asyncapi.com/definitions/2.4.0/specificationExtension.json|g" 5 | find ./bindings/ -name .git -prune -o -type f -not -name prepare_bindings.sh -print0 | xargs -0 perl -i -pe 's|"title"|"x-title"|g' 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /spec-2.0.0/doc.go: -------------------------------------------------------------------------------- 1 | // Package spec provides Go structures to read and write AsyncAPI 2.0.0 entities. 2 | package spec 3 | -------------------------------------------------------------------------------- /spec-2.0.0/entities_test.go: -------------------------------------------------------------------------------- 1 | package spec_test 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | "github.com/swaggest/assertjson" 11 | "github.com/swaggest/go-asyncapi/spec-2.0.0" 12 | ) 13 | 14 | func TestInfo_MarshalJSON(t *testing.T) { 15 | i := spec.Info{ 16 | Title: "foo", 17 | Version: "v1", 18 | MapOfAnything: map[string]interface{}{ 19 | "x-two": "two", 20 | "x-one": 1, 21 | }, 22 | } 23 | 24 | res, err := json.Marshal(i) 25 | assert.NoError(t, err) 26 | assert.Equal(t, `{"title":"foo","version":"v1","x-one":1,"x-two":"two"}`, string(res)) 27 | } 28 | 29 | func TestInfo_MarshalJSON_Nil(t *testing.T) { 30 | i := spec.Info{ 31 | Title: "foo", 32 | Version: "v1", 33 | } 34 | 35 | res, err := json.Marshal(i) 36 | assert.NoError(t, err) 37 | assert.Equal(t, `{"title":"foo","version":"v1"}`, string(res)) 38 | } 39 | 40 | func TestInfo_UnmarshalJSON(t *testing.T) { 41 | i := spec.Info{ 42 | Title: "foo", 43 | } 44 | 45 | err := json.Unmarshal([]byte(`{"title":"foo","version":"v1","x-one":1,"x-two":"two"}`), &i) 46 | assert.NoError(t, err) 47 | assert.Equal(t, 1.0, i.MapOfAnything["x-one"].(float64)) 48 | assert.Equal(t, "two", i.MapOfAnything["x-two"]) 49 | } 50 | 51 | func TestAsyncAPI_UnmarshalJSON_roundTrip(t *testing.T) { 52 | data, err := os.ReadFile("../resources/fixtures/streetlights-2.0.0.json") 53 | require.NoError(t, err) 54 | 55 | var a spec.AsyncAPI 56 | err = json.Unmarshal(data, &a) 57 | require.NoError(t, err) 58 | 59 | roundTripped, err := json.Marshal(a) 60 | require.NoError(t, err) 61 | 62 | assertjson.Equal(t, data, roundTripped) 63 | } 64 | 65 | func TestAsyncAPI_UnmarshalYAML(t *testing.T) { 66 | data, err := os.ReadFile("../resources/fixtures/streetlights-2.0.0.yml") 67 | require.NoError(t, err) 68 | 69 | var a spec.AsyncAPI 70 | err = a.UnmarshalYAML(data) 71 | require.NoError(t, err) 72 | 73 | assert.Equal(t, "#/components/messages/lightMeasured", a.Channels["smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured"].Publish.Message.Reference.Ref) 74 | 75 | marshaledData, err := a.MarshalYAML() 76 | require.NoError(t, err) 77 | 78 | assert.Equal(t, string(data), string(marshaledData)) 79 | } 80 | -------------------------------------------------------------------------------- /spec-2.0.0/yaml.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | // UnmarshalYAML reads from YAML bytes. 13 | func (i *AsyncAPI) UnmarshalYAML(data []byte) error { 14 | var v interface{} 15 | 16 | err := yaml.Unmarshal(data, &v) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | v = convertMapI2MapS(v) 22 | 23 | data, err = json.Marshal(v) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | return i.UnmarshalJSON(data) 29 | } 30 | 31 | // MarshalYAML produces YAML bytes. 32 | func (i *AsyncAPI) MarshalYAML() ([]byte, error) { 33 | jsonData, err := i.MarshalJSON() 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | var v orderedMap 39 | 40 | err = json.Unmarshal(jsonData, &v) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return yaml.Marshal(yaml.MapSlice(v)) 46 | } 47 | 48 | type orderedMap []yaml.MapItem 49 | 50 | func (om *orderedMap) UnmarshalJSON(data []byte) error { 51 | keys, err := objectKeys(data) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | var mapData map[string]json.RawMessage 57 | 58 | err = json.Unmarshal(data, &mapData) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | for _, key := range keys { 64 | jsonVal := mapData[key] 65 | _, err = objectKeys(jsonVal) 66 | 67 | var val interface{} 68 | 69 | if err == nil { 70 | v := make(orderedMap, 0) 71 | 72 | err = json.Unmarshal(jsonVal, &v) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | val = yaml.MapSlice(v) 78 | } else { 79 | err = json.Unmarshal(jsonVal, &val) 80 | if err != nil { 81 | return err 82 | } 83 | } 84 | 85 | *om = append(*om, yaml.MapItem{ 86 | Key: key, 87 | Value: val, 88 | }) 89 | } 90 | 91 | return nil 92 | } 93 | 94 | func objectKeys(b []byte) ([]string, error) { 95 | d := json.NewDecoder(bytes.NewReader(b)) 96 | 97 | t, err := d.Token() 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | if t != json.Delim('{') { 103 | return nil, errors.New("expected start of object") 104 | } 105 | 106 | var keys []string 107 | 108 | for { 109 | t, err := d.Token() 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | if t == json.Delim('}') { 115 | return keys, nil 116 | } 117 | 118 | keys = append(keys, t.(string)) 119 | 120 | if err := skipValue(d); err != nil { 121 | return nil, err 122 | } 123 | } 124 | } 125 | 126 | var errEnd = errors.New("invalid errEnd of array or object") 127 | 128 | func skipValue(d *json.Decoder) error { 129 | t, err := d.Token() 130 | if err != nil { 131 | return err 132 | } 133 | 134 | switch t { 135 | case json.Delim('['), json.Delim('{'): 136 | for { 137 | if err := skipValue(d); err != nil { 138 | if errors.Is(err, errEnd) { 139 | break 140 | } 141 | 142 | return err 143 | } 144 | } 145 | case json.Delim(']'), json.Delim('}'): 146 | return errEnd 147 | } 148 | 149 | return nil 150 | } 151 | 152 | // convertMapI2MapS walks the given dynamic object recursively, and 153 | // converts maps with interface{} key type to maps with string key type. 154 | // This function comes handy if you want to marshal a dynamic object into 155 | // JSON where maps with interface{} key type are not allowed. 156 | // 157 | // Recursion is implemented into values of the following types: 158 | // 159 | // -map[interface{}]interface{} 160 | // -map[string]interface{} 161 | // -[]interface{} 162 | // 163 | // When converting map[interface{}]interface{} to map[string]interface{}, 164 | // fmt.Sprint() with default formatting is used to convert the key to a string key. 165 | // 166 | // See github.com/icza/dyno. 167 | func convertMapI2MapS(v interface{}) interface{} { 168 | switch x := v.(type) { 169 | case map[interface{}]interface{}: 170 | m := map[string]interface{}{} 171 | 172 | for k, v2 := range x { 173 | switch k2 := k.(type) { 174 | case string: // Fast check if it's already a string 175 | m[k2] = convertMapI2MapS(v2) 176 | default: 177 | m[fmt.Sprint(k)] = convertMapI2MapS(v2) 178 | } 179 | } 180 | 181 | v = m 182 | 183 | case []interface{}: 184 | for i, v2 := range x { 185 | x[i] = convertMapI2MapS(v2) 186 | } 187 | 188 | case map[string]interface{}: 189 | for k, v2 := range x { 190 | x[k] = convertMapI2MapS(v2) 191 | } 192 | } 193 | 194 | return v 195 | } 196 | -------------------------------------------------------------------------------- /spec-2.1.0/doc.go: -------------------------------------------------------------------------------- 1 | // Package spec provides Go structures to read and write AsyncAPI 2.1.0 entities. 2 | package spec 3 | -------------------------------------------------------------------------------- /spec-2.1.0/entities_test.go: -------------------------------------------------------------------------------- 1 | package spec_test 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | "github.com/swaggest/assertjson" 11 | "github.com/swaggest/go-asyncapi/spec-2.1.0" 12 | ) 13 | 14 | func TestInfo_MarshalJSON(t *testing.T) { 15 | i := spec.Info{ 16 | Title: "foo", 17 | Version: "v1", 18 | MapOfAnything: map[string]interface{}{ 19 | "x-two": "two", 20 | "x-one": 1, 21 | }, 22 | } 23 | 24 | res, err := json.Marshal(i) 25 | assert.NoError(t, err) 26 | assert.Equal(t, `{"title":"foo","version":"v1","x-one":1,"x-two":"two"}`, string(res)) 27 | } 28 | 29 | func TestInfo_MarshalJSON_Nil(t *testing.T) { 30 | i := spec.Info{ 31 | Title: "foo", 32 | Version: "v1", 33 | } 34 | 35 | res, err := json.Marshal(i) 36 | assert.NoError(t, err) 37 | assert.Equal(t, `{"title":"foo","version":"v1"}`, string(res)) 38 | } 39 | 40 | func TestInfo_UnmarshalJSON(t *testing.T) { 41 | i := spec.Info{} 42 | 43 | err := json.Unmarshal([]byte(`{"title":"foo","version":"v1","x-one":1,"x-two":"two"}`), &i) 44 | require.NoError(t, err) 45 | assert.Equal(t, 1.0, i.MapOfAnything["x-one"].(float64)) 46 | assert.Equal(t, "two", i.MapOfAnything["x-two"]) 47 | } 48 | 49 | func TestAsyncAPI_UnmarshalJSON_roundTrip(t *testing.T) { 50 | data, err := os.ReadFile("../resources/fixtures/streetlights-2.1.0-kafka.json") 51 | require.NoError(t, err) 52 | 53 | var a spec.AsyncAPI 54 | err = json.Unmarshal(data, &a) 55 | require.NoError(t, err) 56 | 57 | roundTripped, err := json.Marshal(a) 58 | require.NoError(t, err) 59 | 60 | assertjson.Equal(t, data, roundTripped) 61 | } 62 | 63 | func TestAsyncAPI_UnmarshalYAML(t *testing.T) { 64 | data, err := os.ReadFile("../resources/fixtures/streetlights-2.1.0-kafka.yml") 65 | require.NoError(t, err) 66 | 67 | var a spec.AsyncAPI 68 | err = a.UnmarshalYAML(data) 69 | require.NoError(t, err) 70 | 71 | assert.Equal(t, 72 | "#/components/messages/lightMeasured", 73 | a.Channels["smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured"]. 74 | Publish. 75 | Message. 76 | Reference. 77 | Ref) 78 | 79 | marshaledData, err := a.MarshalYAML() 80 | require.NoError(t, err) 81 | 82 | assert.Equal(t, string(data), string(marshaledData)) 83 | } 84 | -------------------------------------------------------------------------------- /spec-2.1.0/yaml.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | // UnmarshalYAML reads from YAML bytes. 13 | func (i *AsyncAPI) UnmarshalYAML(data []byte) error { 14 | var v interface{} 15 | 16 | err := yaml.Unmarshal(data, &v) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | v = convertMapI2MapS(v) 22 | 23 | data, err = json.Marshal(v) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | return i.UnmarshalJSON(data) 29 | } 30 | 31 | // MarshalYAML produces YAML bytes. 32 | func (i *AsyncAPI) MarshalYAML() ([]byte, error) { 33 | jsonData, err := i.MarshalJSON() 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | var v orderedMap 39 | 40 | err = json.Unmarshal(jsonData, &v) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return yaml.Marshal(yaml.MapSlice(v)) 46 | } 47 | 48 | type orderedMap []yaml.MapItem 49 | 50 | func (om *orderedMap) UnmarshalJSON(data []byte) error { 51 | keys, err := objectKeys(data) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | var mapData map[string]json.RawMessage 57 | 58 | err = json.Unmarshal(data, &mapData) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | for _, key := range keys { 64 | jsonVal := mapData[key] 65 | _, err = objectKeys(jsonVal) 66 | 67 | var val interface{} 68 | 69 | if err == nil { 70 | v := make(orderedMap, 0) 71 | 72 | err = json.Unmarshal(jsonVal, &v) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | val = yaml.MapSlice(v) 78 | } else { 79 | err = json.Unmarshal(jsonVal, &val) 80 | if err != nil { 81 | return err 82 | } 83 | } 84 | 85 | *om = append(*om, yaml.MapItem{ 86 | Key: key, 87 | Value: val, 88 | }) 89 | } 90 | 91 | return nil 92 | } 93 | 94 | func objectKeys(b []byte) ([]string, error) { 95 | d := json.NewDecoder(bytes.NewReader(b)) 96 | 97 | t, err := d.Token() 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | if t != json.Delim('{') { 103 | return nil, errors.New("expected start of object") 104 | } 105 | 106 | var keys []string 107 | 108 | for { 109 | t, err := d.Token() 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | if t == json.Delim('}') { 115 | return keys, nil 116 | } 117 | 118 | keys = append(keys, t.(string)) 119 | 120 | if err := skipValue(d); err != nil { 121 | return nil, err 122 | } 123 | } 124 | } 125 | 126 | var errEnd = errors.New("invalid errEnd of array or object") 127 | 128 | func skipValue(d *json.Decoder) error { 129 | t, err := d.Token() 130 | if err != nil { 131 | return err 132 | } 133 | 134 | switch t { 135 | case json.Delim('['), json.Delim('{'): 136 | for { 137 | if err := skipValue(d); err != nil { 138 | if errors.Is(err, errEnd) { 139 | break 140 | } 141 | 142 | return err 143 | } 144 | } 145 | case json.Delim(']'), json.Delim('}'): 146 | return errEnd 147 | } 148 | 149 | return nil 150 | } 151 | 152 | // convertMapI2MapS walks the given dynamic object recursively, and 153 | // converts maps with interface{} key type to maps with string key type. 154 | // This function comes handy if you want to marshal a dynamic object into 155 | // JSON where maps with interface{} key type are not allowed. 156 | // 157 | // Recursion is implemented into values of the following types: 158 | // 159 | // -map[interface{}]interface{} 160 | // -map[string]interface{} 161 | // -[]interface{} 162 | // 163 | // When converting map[interface{}]interface{} to map[string]interface{}, 164 | // fmt.Sprint() with default formatting is used to convert the key to a string key. 165 | // 166 | // See github.com/icza/dyno. 167 | func convertMapI2MapS(v interface{}) interface{} { 168 | switch x := v.(type) { 169 | case map[interface{}]interface{}: 170 | m := map[string]interface{}{} 171 | 172 | for k, v2 := range x { 173 | switch k2 := k.(type) { 174 | case string: // Fast check if it's already a string 175 | m[k2] = convertMapI2MapS(v2) 176 | default: 177 | m[fmt.Sprint(k)] = convertMapI2MapS(v2) 178 | } 179 | } 180 | 181 | v = m 182 | 183 | case []interface{}: 184 | for i, v2 := range x { 185 | x[i] = convertMapI2MapS(v2) 186 | } 187 | 188 | case map[string]interface{}: 189 | for k, v2 := range x { 190 | x[k] = convertMapI2MapS(v2) 191 | } 192 | } 193 | 194 | return v 195 | } 196 | -------------------------------------------------------------------------------- /spec-2.4.0/doc.go: -------------------------------------------------------------------------------- 1 | // Package spec provides Go structures to read and write AsyncAPI 2.4.0 entities. 2 | package spec 3 | -------------------------------------------------------------------------------- /spec-2.4.0/helper.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | // AddServer adds named server. 4 | func (i *AsyncAPI) AddServer(name string, srv Server) { 5 | if i.Servers == nil { 6 | i.Servers = make(map[string]ServersAdditionalProperties) 7 | } 8 | 9 | i.Servers[name] = ServersAdditionalProperties{ 10 | Server: &srv, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spec-2.4.0/yaml.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | // UnmarshalYAML reads from YAML bytes. 13 | func (i *AsyncAPI) UnmarshalYAML(data []byte) error { 14 | var v interface{} 15 | 16 | err := yaml.Unmarshal(data, &v) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | v = convertMapI2MapS(v) 22 | 23 | data, err = json.Marshal(v) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | return i.UnmarshalJSON(data) 29 | } 30 | 31 | // MarshalYAML produces YAML bytes. 32 | func (i *AsyncAPI) MarshalYAML() ([]byte, error) { 33 | jsonData, err := i.MarshalJSON() 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | var v orderedMap 39 | 40 | err = json.Unmarshal(jsonData, &v) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return yaml.Marshal(yaml.MapSlice(v)) 46 | } 47 | 48 | type orderedMap []yaml.MapItem 49 | 50 | func (om *orderedMap) UnmarshalJSON(data []byte) error { 51 | keys, err := objectKeys(data) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | var mapData map[string]json.RawMessage 57 | 58 | err = json.Unmarshal(data, &mapData) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | for _, key := range keys { 64 | jsonVal := mapData[key] 65 | _, err = objectKeys(jsonVal) 66 | 67 | var val interface{} 68 | 69 | if err == nil { 70 | v := make(orderedMap, 0) 71 | 72 | err = json.Unmarshal(jsonVal, &v) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | val = yaml.MapSlice(v) 78 | } else { 79 | err = json.Unmarshal(jsonVal, &val) 80 | if err != nil { 81 | return err 82 | } 83 | } 84 | 85 | *om = append(*om, yaml.MapItem{ 86 | Key: key, 87 | Value: val, 88 | }) 89 | } 90 | 91 | return nil 92 | } 93 | 94 | func objectKeys(b []byte) ([]string, error) { 95 | d := json.NewDecoder(bytes.NewReader(b)) 96 | 97 | t, err := d.Token() 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | if t != json.Delim('{') { 103 | return nil, errors.New("expected start of object") 104 | } 105 | 106 | var keys []string 107 | 108 | for { 109 | t, err := d.Token() 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | if t == json.Delim('}') { 115 | return keys, nil 116 | } 117 | 118 | keys = append(keys, t.(string)) 119 | 120 | if err := skipValue(d); err != nil { 121 | return nil, err 122 | } 123 | } 124 | } 125 | 126 | var errEnd = errors.New("invalid errEnd of array or object") 127 | 128 | func skipValue(d *json.Decoder) error { 129 | t, err := d.Token() 130 | if err != nil { 131 | return err 132 | } 133 | 134 | switch t { 135 | case json.Delim('['), json.Delim('{'): 136 | for { 137 | if err := skipValue(d); err != nil { 138 | if errors.Is(err, errEnd) { 139 | break 140 | } 141 | 142 | return err 143 | } 144 | } 145 | case json.Delim(']'), json.Delim('}'): 146 | return errEnd 147 | } 148 | 149 | return nil 150 | } 151 | 152 | // convertMapI2MapS walks the given dynamic object recursively, and 153 | // converts maps with interface{} key type to maps with string key type. 154 | // This function comes handy if you want to marshal a dynamic object into 155 | // JSON where maps with interface{} key type are not allowed. 156 | // 157 | // Recursion is implemented into values of the following types: 158 | // 159 | // -map[interface{}]interface{} 160 | // -map[string]interface{} 161 | // -[]interface{} 162 | // 163 | // When converting map[interface{}]interface{} to map[string]interface{}, 164 | // fmt.Sprint() with default formatting is used to convert the key to a string key. 165 | // 166 | // See github.com/icza/dyno. 167 | func convertMapI2MapS(v interface{}) interface{} { 168 | switch x := v.(type) { 169 | case map[interface{}]interface{}: 170 | m := map[string]interface{}{} 171 | 172 | for k, v2 := range x { 173 | switch k2 := k.(type) { 174 | case string: // Fast check if it's already a string 175 | m[k2] = convertMapI2MapS(v2) 176 | default: 177 | m[fmt.Sprint(k)] = convertMapI2MapS(v2) 178 | } 179 | } 180 | 181 | v = m 182 | 183 | case []interface{}: 184 | for i, v2 := range x { 185 | x[i] = convertMapI2MapS(v2) 186 | } 187 | 188 | case map[string]interface{}: 189 | for k, v2 := range x { 190 | x[k] = convertMapI2MapS(v2) 191 | } 192 | } 193 | 194 | return v 195 | } 196 | -------------------------------------------------------------------------------- /spec/doc.go: -------------------------------------------------------------------------------- 1 | // Package spec provides Go structures to read and write AsyncAPI 1.2.0 entities. 2 | // 3 | // Deprecated: upgrade to AsyncAPI 2.0.0 and use github.com/swaggest/go-asyncapi/spec-2.0.0. 4 | package spec 5 | -------------------------------------------------------------------------------- /spec/entities_test.go: -------------------------------------------------------------------------------- 1 | package spec_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/swaggest/go-asyncapi/spec" 9 | ) 10 | 11 | func TestInfo_MarshalJSON(t *testing.T) { 12 | i := spec.Info{ 13 | Version: "v1", 14 | MapOfAnythingValues: map[string]interface{}{ 15 | "x-two": "two", 16 | "x-one": 1, 17 | }, 18 | } 19 | 20 | res, err := json.Marshal(i) 21 | assert.NoError(t, err) 22 | assert.Equal(t, `{"version":"v1","x-one":1,"x-two":"two"}`, string(res)) 23 | } 24 | 25 | func TestInfo_MarshalJSON_Nil(t *testing.T) { 26 | i := spec.Info{ 27 | Version: "v1", 28 | } 29 | 30 | res, err := json.Marshal(i) 31 | assert.NoError(t, err) 32 | assert.Equal(t, `{"version":"v1"}`, string(res)) 33 | } 34 | 35 | func TestInfo_UnmarshalJSON(t *testing.T) { 36 | i := spec.Info{} 37 | 38 | err := json.Unmarshal([]byte(`{"version":"v1","x-one":1,"x-two":"two"}`), &i) 39 | assert.NoError(t, err) 40 | assert.Equal(t, 1.0, i.MapOfAnythingValues["x-one"].(float64)) 41 | assert.Equal(t, "two", i.MapOfAnythingValues["x-two"]) 42 | } 43 | 44 | func TestAPIKey_UnmarshalJSON(t *testing.T) { 45 | data := []byte(`asyncapi: '1.2.0' 46 | info: 47 | title: Streetlights API 48 | version: '1.0.0' 49 | description: | 50 | The Smartylighting Streetlights API allows you to remotely manage the city lights. 51 | 52 | ### Check out its awesome features: 53 | 54 | * Turn a specific streetlight on/off 🌃 55 | * Dim a specific streetlight 😎 56 | * Receive real-time information about environmental lighting conditions 📈 57 | license: 58 | name: Apache 2.0 59 | url: https://www.apache.org/licenses/LICENSE-2.0 60 | baseTopic: smartylighting.streetlights.1.0 61 | 62 | servers: 63 | - url: api.streetlights.smartylighting.com:{port} 64 | scheme: mqtt 65 | description: Test broker 66 | variables: 67 | port: 68 | description: Secure connection (TLS) is available through port 8883. 69 | default: '1883' 70 | enum: 71 | - '1883' 72 | - '8883' 73 | 74 | security: 75 | - apiKey: [] 76 | 77 | topics: 78 | event.{streetlightId}.lighting.measured: 79 | parameters: 80 | - $ref: '#/components/parameters/streetlightId' 81 | publish: 82 | $ref: '#/components/messages/lightMeasured' 83 | 84 | action.{streetlightId}.turn.on: 85 | parameters: 86 | - $ref: '#/components/parameters/streetlightId' 87 | subscribe: 88 | $ref: '#/components/messages/turnOnOff' 89 | 90 | action.{streetlightId}.turn.off: 91 | parameters: 92 | - $ref: '#/components/parameters/streetlightId' 93 | subscribe: 94 | $ref: '#/components/messages/turnOnOff' 95 | 96 | action.{streetlightId}.dim: 97 | parameters: 98 | - $ref: '#/components/parameters/streetlightId' 99 | subscribe: 100 | $ref: '#/components/messages/dimLight' 101 | 102 | components: 103 | messages: 104 | lightMeasured: 105 | summary: Inform about environmental lighting conditions for a particular streetlight. 106 | payload: 107 | $ref: "#/components/schemas/lightMeasuredPayload" 108 | turnOnOff: 109 | summary: Command a particular streetlight to turn the lights on or off. 110 | payload: 111 | $ref: "#/components/schemas/turnOnOffPayload" 112 | dimLight: 113 | summary: Command a particular streetlight to dim the lights. 114 | payload: 115 | $ref: "#/components/schemas/dimLightPayload" 116 | 117 | schemas: 118 | lightMeasuredPayload: 119 | type: object 120 | properties: 121 | lumens: 122 | type: integer 123 | minimum: 0 124 | description: Light intensity measured in lumens. 125 | sentAt: 126 | $ref: "#/components/schemas/sentAt" 127 | turnOnOffPayload: 128 | type: object 129 | properties: 130 | command: 131 | type: string 132 | enum: 133 | - on 134 | - off 135 | description: Whether to turn on or off the light. 136 | sentAt: 137 | $ref: "#/components/schemas/sentAt" 138 | dimLightPayload: 139 | type: object 140 | properties: 141 | percentage: 142 | type: integer 143 | description: Percentage to which the light should be dimmed to. 144 | minimum: 0 145 | maximum: 100 146 | sentAt: 147 | $ref: "#/components/schemas/sentAt" 148 | sentAt: 149 | type: string 150 | format: date-time 151 | description: Date and time when the message was sent. 152 | 153 | securitySchemes: 154 | apiKey: 155 | type: apiKey 156 | in: user 157 | description: Provide your API key as the user and leave the password empty. 158 | 159 | parameters: 160 | streetlightId: 161 | name: streetlightId 162 | description: The ID of the streetlight. 163 | schema: 164 | type: string 165 | 166 | `) 167 | 168 | var a spec.AsyncAPI 169 | err := a.UnmarshalYAML(data) 170 | assert.NoError(t, err) 171 | 172 | assert.Equal(t, "#/components/messages/lightMeasured", a.Topics.MapOfTopicItemValues["event.{streetlightId}.lighting.measured"].Publish.Message.Ref) 173 | 174 | _, err = a.Info.MarshalJSON() 175 | assert.NoError(t, err) 176 | 177 | data, err = a.MarshalYAML() 178 | assert.NoError(t, err) 179 | 180 | expected := `asyncapi: 1.2.0 181 | info: 182 | description: "The Smartylighting Streetlights API allows you to remotely manage 183 | the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight 184 | on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time 185 | information about environmental lighting conditions \U0001F4C8\n" 186 | license: 187 | name: Apache 2.0 188 | url: https://www.apache.org/licenses/LICENSE-2.0 189 | title: Streetlights API 190 | version: 1.0.0 191 | baseTopic: smartylighting.streetlights.1.0 192 | servers: 193 | - description: Test broker 194 | scheme: mqtt 195 | url: api.streetlights.smartylighting.com:{port} 196 | variables: 197 | port: 198 | default: "1883" 199 | description: Secure connection (TLS) is available through port 8883. 200 | enum: 201 | - "1883" 202 | - "8883" 203 | topics: 204 | action.{streetlightId}.dim: 205 | parameters: 206 | - $ref: '#/components/parameters/streetlightId' 207 | subscribe: 208 | $ref: '#/components/messages/dimLight' 209 | action.{streetlightId}.turn.off: 210 | parameters: 211 | - $ref: '#/components/parameters/streetlightId' 212 | subscribe: 213 | $ref: '#/components/messages/turnOnOff' 214 | action.{streetlightId}.turn.on: 215 | parameters: 216 | - $ref: '#/components/parameters/streetlightId' 217 | subscribe: 218 | $ref: '#/components/messages/turnOnOff' 219 | event.{streetlightId}.lighting.measured: 220 | parameters: 221 | - $ref: '#/components/parameters/streetlightId' 222 | publish: 223 | $ref: '#/components/messages/lightMeasured' 224 | components: 225 | messages: 226 | dimLight: 227 | payload: 228 | $ref: '#/components/schemas/dimLightPayload' 229 | summary: Command a particular streetlight to dim the lights. 230 | lightMeasured: 231 | payload: 232 | $ref: '#/components/schemas/lightMeasuredPayload' 233 | summary: Inform about environmental lighting conditions for a particular streetlight. 234 | turnOnOff: 235 | payload: 236 | $ref: '#/components/schemas/turnOnOffPayload' 237 | summary: Command a particular streetlight to turn the lights on or off. 238 | parameters: 239 | streetlightId: 240 | description: The ID of the streetlight. 241 | name: streetlightId 242 | schema: 243 | type: string 244 | schemas: 245 | dimLightPayload: 246 | properties: 247 | percentage: 248 | description: Percentage to which the light should be dimmed to. 249 | maximum: 100 250 | minimum: 0 251 | type: integer 252 | sentAt: 253 | $ref: '#/components/schemas/sentAt' 254 | type: object 255 | lightMeasuredPayload: 256 | properties: 257 | lumens: 258 | description: Light intensity measured in lumens. 259 | minimum: 0 260 | type: integer 261 | sentAt: 262 | $ref: '#/components/schemas/sentAt' 263 | type: object 264 | sentAt: 265 | description: Date and time when the message was sent. 266 | format: date-time 267 | type: string 268 | turnOnOffPayload: 269 | properties: 270 | command: 271 | description: Whether to turn on or off the light. 272 | enum: 273 | - true 274 | - false 275 | type: string 276 | sentAt: 277 | $ref: '#/components/schemas/sentAt' 278 | type: object 279 | securitySchemes: 280 | apiKey: 281 | description: Provide your API key as the user and leave the password empty. 282 | in: user 283 | type: apiKey 284 | security: 285 | - apiKey: [] 286 | ` 287 | assert.Equal(t, expected, string(data)) 288 | } 289 | -------------------------------------------------------------------------------- /spec/yaml.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | // UnmarshalYAML reads from YAML bytes. 13 | func (i *AsyncAPI) UnmarshalYAML(data []byte) error { 14 | var v interface{} 15 | 16 | err := yaml.Unmarshal(data, &v) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | v = convertMapI2MapS(v) 22 | 23 | data, err = json.Marshal(v) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | return i.UnmarshalJSON(data) 29 | } 30 | 31 | // MarshalYAML produces YAML bytes. 32 | func (i *AsyncAPI) MarshalYAML() ([]byte, error) { 33 | // return ya.Marshal(i) 34 | jsonData, err := i.MarshalJSON() 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | var v orderedMap 40 | 41 | err = json.Unmarshal(jsonData, &v) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return yaml.Marshal(yaml.MapSlice(v)) 47 | } 48 | 49 | type orderedMap []yaml.MapItem 50 | 51 | func (om *orderedMap) UnmarshalJSON(data []byte) error { 52 | var mapData map[string]interface{} 53 | 54 | err := json.Unmarshal(data, &mapData) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | keys, err := objectKeys(data) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | for _, key := range keys { 65 | *om = append(*om, yaml.MapItem{ 66 | Key: key, 67 | Value: mapData[key], 68 | }) 69 | } 70 | 71 | return nil 72 | } 73 | 74 | func objectKeys(b []byte) ([]string, error) { 75 | d := json.NewDecoder(bytes.NewReader(b)) 76 | 77 | t, err := d.Token() 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | if t != json.Delim('{') { 83 | return nil, errors.New("expected start of object") 84 | } 85 | 86 | var keys []string 87 | 88 | for { 89 | t, err := d.Token() 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | if t == json.Delim('}') { 95 | return keys, nil 96 | } 97 | 98 | keys = append(keys, t.(string)) 99 | 100 | if err := skipValue(d); err != nil { 101 | return nil, err 102 | } 103 | } 104 | } 105 | 106 | var errEnd = errors.New("invalid errEnd of array or object") 107 | 108 | func skipValue(d *json.Decoder) error { 109 | t, err := d.Token() 110 | if err != nil { 111 | return err 112 | } 113 | 114 | switch t { 115 | case json.Delim('['), json.Delim('{'): 116 | for { 117 | if err := skipValue(d); err != nil { 118 | if errors.Is(err, errEnd) { 119 | break 120 | } 121 | 122 | return err 123 | } 124 | } 125 | case json.Delim(']'), json.Delim('}'): 126 | return errEnd 127 | } 128 | 129 | return nil 130 | } 131 | 132 | // convertMapI2MapS walks the given dynamic object recursively, and 133 | // converts maps with interface{} key type to maps with string key type. 134 | // This function comes handy if you want to marshal a dynamic object into 135 | // JSON where maps with interface{} key type are not allowed. 136 | // 137 | // Recursion is implemented into values of the following types: 138 | // 139 | // -map[interface{}]interface{} 140 | // -map[string]interface{} 141 | // -[]interface{} 142 | // 143 | // When converting map[interface{}]interface{} to map[string]interface{}, 144 | // fmt.Sprint() with default formatting is used to convert the key to a string key. 145 | // 146 | // See github.com/icza/dyno. 147 | func convertMapI2MapS(v interface{}) interface{} { 148 | switch x := v.(type) { 149 | case map[interface{}]interface{}: 150 | m := map[string]interface{}{} 151 | 152 | for k, v2 := range x { 153 | switch k2 := k.(type) { 154 | case string: // Fast check if it's already a string 155 | m[k2] = convertMapI2MapS(v2) 156 | default: 157 | m[fmt.Sprint(k)] = convertMapI2MapS(v2) 158 | } 159 | } 160 | 161 | v = m 162 | 163 | case []interface{}: 164 | for i, v2 := range x { 165 | x[i] = convertMapI2MapS(v2) 166 | } 167 | 168 | case map[string]interface{}: 169 | for k, v2 := range x { 170 | x[k] = convertMapI2MapS(v2) 171 | } 172 | } 173 | 174 | return v 175 | } 176 | --------------------------------------------------------------------------------