on master help sign in
122 files changed+3418−1701
since splitsnapshots
unifiedsplit
.github/actions/common_setup/action.yaml+42 .github/workflows/github-actions-image.yaml+311 .github/workflows/haskell.yml−54 .github/workflows/remove-artifacts.yaml−17 CHANGELOG.md+1−1 README.md+245−13 action.yaml+47 app/Data/OpenApi/Compare/Options.hs+134 app/FormatHeuristic.hs+3−3 app/Main.hs+4−3 app/OpenAPI/Checker/Options.hs−115 benchmarks/README.md+2−2 benchmarks/default.nix+8−12 ci/policeman.nix+4 openapi-diff.cabal → compaREST.cabal+83−56 default.nix+114 docs/Development_guide.md+149 docs/Integration_guide.md+51 docs/User_guide.md+51 docs/img/github-action-report.png github-action/CompaREST/GitHub/API.hs+86 github-action/CompaREST/GitHub/Action/Config.hs+43 github-action/Control/Monad/Freer/GitHub.hs+27 github-action/GitHub/Data/Checks.hs+173 github-action/GitHub/Endpoints/Checks.hs+12 github-action/Main.hs+66 nix/ci.nix+6 nix/sources.json+50 nix/sources.nix+174 policeman.json+6 src/Data/HList.hs+11−11 src/OpenAPI/Checker/Behavior.hs → src/Data/OpenApi/Compare/Behavior.hs+13−13 src/OpenAPI/Checker/Common.hs → src/Data/OpenApi/Compare/Common.hs+2−2 src/OpenAPI/Checker/Formula.hs → src/Data/OpenApi/Compare/Formula.hs+35−25 src/Data/OpenApi/Compare/Memo.hs+164 src/OpenAPI/Checker/Orphans.hs → src/Data/OpenApi/Compare/Orphans.hs+1−1 src/OpenAPI/Checker/Paths.hs → src/Data/OpenApi/Compare/Paths.hs+10−10 src/OpenAPI/Checker/PathsPrefixTree.hs → src/Data/OpenApi/Compare/PathsPrefixTree.hs+39−39 src/OpenAPI/Checker/References.hs → src/Data/OpenApi/Compare/References.hs+11−12 src/OpenAPI/Checker/Report.hs → src/Data/OpenApi/Compare/Report.hs+152−131 src/OpenAPI/Checker/Report/Html/Template.hs → src/Data/OpenApi/Compare/Report/Html/Template.hs+4−4 src/OpenAPI/Checker/Report/Jet.hs → src/Data/OpenApi/Compare/Report/Jet.hs+5−5 src/OpenAPI/Checker/Run.hs → src/Data/OpenApi/Compare/Run.hs+10−9 src/OpenAPI/Checker/Subtree.hs → src/Data/OpenApi/Compare/Subtree.hs+128−126 src/OpenAPI/Checker/Validate/Header.hs → src/Data/OpenApi/Compare/Validate/Header.hs+5−5 src/OpenAPI/Checker/Validate/Link.hs → src/Data/OpenApi/Compare/Validate/Link.hs+4−4 src/OpenAPI/Checker/Validate/MediaTypeObject.hs → src/Data/OpenApi/Compare/Validate/MediaTypeObject.hs+10−10 src/OpenAPI/Checker/Validate/OAuth2Flows.hs → src/Data/OpenApi/Compare/Validate/OAuth2Flows.hs+17−17 src/OpenAPI/Checker/Validate/OpenApi.hs → src/Data/OpenApi/Compare/Validate/OpenApi.hs+18−17 src/OpenAPI/Checker/Validate/Operation.hs → src/Data/OpenApi/Compare/Validate/Operation.hs+43−41 src/OpenAPI/Checker/Validate/Param.hs → src/Data/OpenApi/Compare/Validate/Param.hs+14−12 src/OpenAPI/Checker/Validate/PathFragment.hs → src/Data/OpenApi/Compare/Validate/PathFragment.hs+8−9 src/Data/OpenApi/Compare/Validate/Products.hs+48 src/OpenAPI/Checker/Validate/RequestBody.hs → src/Data/OpenApi/Compare/Validate/RequestBody.hs+7−7 src/OpenAPI/Checker/Validate/Responses.hs → src/Data/OpenApi/Compare/Validate/Responses.hs+11−11 src/OpenAPI/Checker/Validate/Schema.hs → src/Data/OpenApi/Compare/Validate/Schema.hs+95−115 src/OpenAPI/Checker/Validate/Schema/DNF.hs → src/Data/OpenApi/Compare/Validate/Schema/DNF.hs+9−9 src/OpenAPI/Checker/Validate/Schema/Issues.hs → src/Data/OpenApi/Compare/Validate/Schema/Issues.hs+61−59 src/OpenAPI/Checker/Validate/Schema/JsonFormula.hs → src/Data/OpenApi/Compare/Validate/Schema/JsonFormula.hs+55−48 src/OpenAPI/Checker/Validate/Schema/Partition.hs → src/Data/OpenApi/Compare/Validate/Schema/Partition.hs+38−37 src/OpenAPI/Checker/Validate/Schema/Process.hs → src/Data/OpenApi/Compare/Validate/Schema/Process.hs+48−47 src/OpenAPI/Checker/Validate/Schema/Traced.hs → src/Data/OpenApi/Compare/Validate/Schema/Traced.hs+14−14 src/OpenAPI/Checker/Validate/Schema/TypedJson.hs → src/Data/OpenApi/Compare/Validate/Schema/TypedJson.hs+26−24 src/OpenAPI/Checker/Validate/SecurityRequirement.hs → src/Data/OpenApi/Compare/Validate/SecurityRequirement.hs+14−13 src/OpenAPI/Checker/Validate/SecurityScheme.hs → src/Data/OpenApi/Compare/Validate/SecurityScheme.hs+5−5 src/OpenAPI/Checker/Validate/Server.hs → src/Data/OpenApi/Compare/Validate/Server.hs+17−16 src/Data/OpenApi/Compare/Validate/Sums.hs+25 src/Data/OpenUnion/Extra.hs+5−5 src/OpenAPI/Checker/Memo.hs−126 src/OpenAPI/Checker/Validate/Products.hs−49 src/OpenAPI/Checker/Validate/Sums.hs−24 stack.yaml+7−4 stack.yaml.lock+25−4 test/README.md+1−1 test/Spec/Golden/Extra.hs+46−42 test/Spec/Golden/TraceTree.hs+9−6 test/golden/common/bad-oneof-unchanged/report.md+6−6 test/golden/common/enum-added/report.md+7−7 test/golden/common/enum-anyof/report.md+3−3 test/golden/common/id/report.md+3−3 test/golden/common/json/recursive/report.md+3−3 test/golden/common/maximum-lowered/report.md+7−7 test/golden/common/pathItem/operation/parameters/allowEmptyValue/reset/report.md+4−4 test/golden/common/pathItem/operation/parameters/allowEmptyValue/set/report.md+4−4 test/golden/common/pathItem/operation/parameters/change/report.md+5−5 test/golden/common/pathItem/operation/parameters/required/false/add/report.md+3−3 test/golden/common/pathItem/operation/parameters/required/false/del/report.md+3−3 test/golden/common/pathItem/operation/parameters/required/reset/report.md+4−4 test/golden/common/pathItem/operation/parameters/required/set/report.md+4−4 test/golden/common/pathItem/operation/parameters/required/true/add/report.md+4−4 test/golden/common/pathItem/operation/parameters/required/true/del/report.md+4−4 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/add/report.md+4−4 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/change/report.md+7−7 test/golden/common/pathItem/operation/requestBody/mediaTypeObject/del/report.md+4−4 test/golden/common/pathItem/operation/requestBody/required/reset/report.md+4−4 test/golden/common/pathItem/operation/requestBody/required/set/report.md+4−4 test/golden/common/pathItem/operation/responses/add/report.md+4−4 test/golden/common/pathItem/operation/responses/change/headers/mandatory/add/report.md+4−4 test/golden/common/pathItem/operation/responses/change/headers/mandatory/del/report.md+4−4 test/golden/common/pathItem/operation/responses/change/headers/optional/add/report.md+3−3 test/golden/common/pathItem/operation/responses/change/headers/optional/del/report.md+3−3 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/add/report.md+4−4 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/change/report.md+7−7 test/golden/common/pathItem/operation/responses/change/mediaTypeObject/del/report.md+4−4 test/golden/common/pathItem/operation/responses/del/report.md+4−4 test/golden/common/property-removed-additional/report.md+7−7 test/golden/common/property-removed/report.md+6−6 test/golden/common/property-required/report.md+7−7 test/golden/common/random-example/report.md+9−9 test/golden/common/recursive/report.md+7−7 test/golden/common/security-scheme/report.md+5−5 test/golden/common/servers/report.md+5−5 test/golden/common/tuple-items/heterogeneous-component-mismatch/report.md+9−9 test/golden/common/tuple-items/heterogeneous-length-mismatch/report.md+6−6 test/golden/common/tuple-items/homogeneous-component-mismatch/report.md+9−9 test/golden/common/tuple-items/homogeneous-length-mismatch/report.md+13−13 test/golden/common/tuple-items/homogeneous-vector/report.md+3−3 test/golden/common/type-changing/report.md+15−15 test/golden/common/unguarded-recursive/report.md+6−6 test/golden/common/variant-added/report.md+7−7 test/golden/common/variant-changed/report.md+9−9 test/golden/common/variant-deep-changed/report.md+9−9
.github/actions/common_setup/action.yamlnew file+42
@@ -0,0 +1,42 @@
1+name: "Common setup"
2+description: "Common setup steps"
3+inputs:
4+ authToken:
5+ description: "Cachix auth token"
6+ required: true
7+runs:
8+ using: "composite"
9+ steps:
10+ - name: Increase swap if linux
11+ shell: bash
12+ run: |
13+ if [ "$RUNNER_OS" == "Linux" ]; then
14+
15+ echo "Is Linux"
16+
17+ free -h
18+
19+ sudo swapoff /mnt/swapfile
20+ sudo fallocate -l 12G /mnt/swapfile
21+ sudo mkswap /mnt/swapfile
22+ sudo swapon /mnt/swapfile
23+
24+ free -h
25+
26+ else
27+
28+ echo "Not Linux"
29+
30+ fi
31+
32+ - name: Install Nix
33+ uses: cachix/install-nix-action@v16
34+ with:
35+ extra_nix_config: |
36+ system-features = kvm
37+ trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
38+ substituters = https://hydra.iohk.io https://cache.nixos.org/
39+ - uses: cachix/cachix-action@v10
40+ with:
41+ name: typeable
42+ authToken: "${{ inputs.authToken }}"
.github/workflows/github-actions-image.yamlnew file+311
@@ -0,0 +1,311 @@
1+name: Github Action Docker Image
2+
3+on:
4+ push:
5+ branches: [master]
6+ pull_request:
7+ branches: [master]
8+ release:
9+ types: [prereleased]
10+
11+jobs:
12+ check-formatting:
13+ name: Check Haskell formatting
14+ runs-on: ubuntu-latest
15+ steps:
16+ - uses: actions/checkout@v2
17+
18+ - name: Common setup
19+ uses: ./.github/actions/common_setup
20+ with:
21+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
22+
23+ - name: Run fourmolu
24+ run: |
25+ nix-shell nix/ci.nix -j auto --run "git ls-files '*.hs' | xargs fourmolu -m inplace -o -XRecursiveDo -o -XTypeApplications -o -XPatternSynonyms -o -XBangPatterns"
26+ git diff --exit-code
27+
28+ policeman:
29+ runs-on: ubuntu-latest
30+ steps:
31+ - uses: actions/checkout@v2
32+ with:
33+ submodules: recursive
34+
35+ - name: Common setup
36+ uses: ./.github/actions/common_setup
37+ with:
38+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
39+
40+ - name: Run policeman
41+ run: |
42+ jq --argjson msg "$( $(nix-build ci/policeman.nix)/bin/policeman | grep -v "⚙" | jq -aRs . )" ".[0].message = \$msg" <policeman.json >message.json
43+ cat message.json
44+
45+ - name: Post notice
46+ uses: yuzutech/annotations-action@v0.4.0
47+ with:
48+ title: 👮 Policeman
49+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
50+ input: "./message.json"
51+
52+ check_versions:
53+ runs-on: ubuntu-latest
54+ steps:
55+ - uses: actions/checkout@v2
56+ with:
57+ submodules: recursive
58+
59+ - name: Check library version
60+ if: github.event_name == 'release'
61+ run: |
62+ if [ "$(awk -F ':' '/^version/ {gsub(/ /, "", $2); print $2}' compaREST.cabal)" != "${{ github.event.release.tag_name }}" ]; then
63+ echo "::error file=compaREST.cabal::Library version does not match release version."
64+ exit 1
65+ fi
66+
67+ - name: Check docker tag
68+ if: github.event_name == 'release'
69+ run: |
70+ if [ "$(yq eval '.runs.image' action.yaml | awk -F ':' '{print $NF}')" != "${{ github.event.release.tag_name }}" ]; then
71+ echo "::error file=action.yaml::Action docker tag does not match release tag."
72+ exit 1
73+ fi
74+
75+ - name: Check changelog
76+ if: github.event_name == 'release'
77+ run: |
78+ if ! grep '^## ${{ github.event.release.tag_name }}$' CHANGELOG.md; then
79+ echo "::error file=CHANGELOG.md::CHANGELOG.md does not contain an entry for this release."
80+ exit 1
81+ fi
82+
83+ Linux:
84+ needs: check_versions
85+ runs-on: ubuntu-latest
86+ steps:
87+ - uses: actions/checkout@v2
88+ with:
89+ submodules: recursive
90+
91+ - name: Common setup
92+ uses: ./.github/actions/common_setup
93+ with:
94+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
95+
96+ - name: Test
97+ shell: bash
98+ run: |
99+ $(nix-build -A test -j auto --no-out-link)/bin/compaREST-tests
100+
101+ - name: Build Linux static binary
102+ shell: bash
103+ run: |
104+ cp $(nix-build -A compaRESTStaticBin -j auto --no-out-link)/bin/compaREST compaREST
105+
106+ - name: Upload compaREST Linux Binary
107+ uses: actions/upload-artifact@v2
108+ with:
109+ name: compaREST-Linux
110+ path: compaREST
111+
112+ - name: Build Docker Images
113+ shell: bash
114+ run: |
115+ cp $(nix-build -A compaRESTGithubAction -j auto --no-out-link) compaREST-Github-Action-image.tar.gz
116+ cp $(nix-build -A compaRESTImage -j auto --no-out-link) compaREST-image.tar.gz
117+
118+ - name: Upload compaREST Docker Github Action Image
119+ uses: actions/upload-artifact@v2
120+ with:
121+ name: compaREST Github Action Docker Image
122+ path: compaREST-Github-Action-image.tar.gz
123+
124+ - name: Upload compaREST Docker Image
125+ uses: actions/upload-artifact@v2
126+ with:
127+ name: compaREST Docker Image
128+ path: compaREST-image.tar.gz
129+
130+ - name: Install skopeo
131+ if: github.event_name == 'release'
132+ shell: bash
133+ run: |
134+ sudo apt-get -y update
135+ sudo apt-get -y install skopeo
136+
137+ - name: Login to DockerHub
138+ uses: docker/login-action@v1.10.0
139+ with:
140+ username: ${{ secrets.DOCKER_HUB_USERNAME }}
141+ password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
142+
143+ - name: Push Nightly Docker Images to DockerHub
144+ if: github.ref == 'refs/heads/master' && github.event_name == 'push'
145+ shell: bash
146+ run: |
147+ skopeo copy docker-archive:$(nix-build -A compaRESTGithubAction -j auto --no-out-link) docker://ilyakooo0/comparest-github-action:nightly
148+ skopeo copy docker-archive:$(nix-build -A compaRESTImage -j auto --no-out-link) docker://ilyakooo0/comparest:nightly
149+
150+ - name: Push Release Docker Images to DockerHub
151+ if: github.event_name == 'release'
152+ shell: bash
153+ run: |
154+ skopeo copy docker-archive:$(nix-build -A compaRESTGithubAction -j auto --no-out-link) docker://ilyakooo0/comparest-github-action:${{ github.event.release.tag_name }}
155+ skopeo copy docker-archive:$(nix-build -A compaRESTImage -j auto --no-out-link) docker://ilyakooo0/comparest:${{ github.event.release.tag_name }}
156+
157+ - name: Zip Release Assets
158+ if: github.event_name == 'release'
159+ shell: bash
160+ run: |
161+ zip compaREST-Linux-x86.zip compaREST
162+
163+ - name: Upload compaREST Github Action Docker Image to release
164+ if: github.event_name == 'release'
165+ uses: svenstaro/upload-release-action@2.2.1
166+ with:
167+ repo_token: ${{ secrets.GITHUB_TOKEN }}
168+ file: compaREST-Github-Action-image.tar.gz
169+ tag: ${{ github.ref }}
170+
171+ - name: Upload compaREST Docker Image to release
172+ if: github.event_name == 'release'
173+ uses: svenstaro/upload-release-action@2.2.1
174+ with:
175+ repo_token: ${{ secrets.GITHUB_TOKEN }}
176+ file: compaREST-image.tar.gz
177+ tag: ${{ github.ref }}
178+
179+ - name: Upload compaREST binary – Linux – x86 to release
180+ if: github.event_name == 'release'
181+ uses: svenstaro/upload-release-action@2.2.1
182+ with:
183+ repo_token: ${{ secrets.GITHUB_TOKEN }}
184+ file: compaREST-Linux-x86.zip
185+ tag: ${{ github.ref }}
186+
187+ dist:
188+ needs: check_versions
189+ runs-on: ubuntu-latest
190+ steps:
191+ - uses: actions/checkout@v2
192+ with:
193+ submodules: recursive
194+
195+ - name: Cache
196+ uses: actions/cache@v1
197+ with:
198+ path: |
199+ ~/.stack
200+ .stack-work
201+ key: ${{ runner.os }}-stack-${{ hashFiles('stack.yaml') }}-${{ hashFiles('**.cabal') }}
202+ restore-keys: |
203+ ${{ runner.os }}-stack-${{ hashFiles('stack.yaml') }}
204+ ${{ runner.os }}-stack
205+
206+ - name: Upgrade stack
207+ shell: bash
208+ # Needed for Hackage token authorization
209+ run: stack upgrade --git
210+
211+ - name: Build dist
212+ run: |
213+ stack sdist --pvp-bounds both --test-tarball
214+ echo "distPath=$(stack path | awk -F ": " '/dist-dir/ {print $2}')" >> $GITHUB_ENV
215+
216+ - name: Upload compaREST sources artifact
217+ uses: actions/upload-artifact@v2
218+ with:
219+ name: compaREST sources
220+ path: ${{ env.distPath }}/*.tar.gz
221+
222+ - name: Upload compaREST sources to release
223+ if: github.event_name == 'release'
224+ uses: svenstaro/upload-release-action@2.2.1
225+ with:
226+ repo_token: ${{ secrets.GITHUB_TOKEN }}
227+ file_glob: true
228+ file: ${{ env.distPath }}/*.tar.gz
229+ tag: ${{ github.ref }}
230+
231+ - name: Publish compaREST candidate to Hackage
232+ shell: bash
233+ if: github.event_name == 'release'
234+ env:
235+ HACKAGE_KEY: ${{ secrets.HACKAGE_KEY }}
236+ run: |
237+ stack upload --pvp-bounds both --test-tarball --candidate .
238+
239+ macOS:
240+ needs: check_versions
241+ runs-on: macos-latest
242+ steps:
243+ - uses: actions/checkout@v2
244+ with:
245+ submodules: recursive
246+
247+ - name: Common setup
248+ uses: ./.github/actions/common_setup
249+ with:
250+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
251+
252+ - name: Build macOS binary bundle
253+ run: |
254+ cp -LR $(nix-build -A macOSCompaRESTBundle -j auto --no-out-link) compaREST
255+
256+ - name: Upload macOS compaREST bundle
257+ uses: actions/upload-artifact@v2
258+ with:
259+ name: compaREST binary – macOS – x86
260+ path: compaREST
261+
262+ - name: Zip Release Assets
263+ if: github.event_name == 'release'
264+ shell: bash
265+ run: |
266+ zip -r compaREST-macOS-x86.zip compaREST
267+
268+ - name: Upload compaREST binary – macOS – x86 to release
269+ if: github.event_name == 'release'
270+ uses: svenstaro/upload-release-action@2.2.1
271+ with:
272+ repo_token: ${{ secrets.GITHUB_TOKEN }}
273+ file: compaREST-macOS-x86.zip
274+ tag: ${{ github.ref }}
275+
276+ Linux-Windows:
277+ needs: check_versions
278+ runs-on: ubuntu-latest
279+ steps:
280+ - uses: actions/checkout@v2
281+ with:
282+ submodules: recursive
283+
284+ - name: Common setup
285+ uses: ./.github/actions/common_setup
286+ with:
287+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
288+
289+ - name: Build Windows binary
290+ run: |
291+ cp -LR $(nix-build -A WindowsCompaRESTBin -j auto --no-out-link)/bin compaREST
292+
293+ - name: Upload Windows compaREST bundle
294+ uses: actions/upload-artifact@v2
295+ with:
296+ name: compaREST binary – Windows
297+ path: compaREST
298+
299+ - name: Zip Release Assets
300+ if: github.event_name == 'release'
301+ shell: bash
302+ run: |
303+ zip -r compaREST-Windows.zip compaREST
304+
305+ - name: Upload compaREST binary – Windows to release
306+ if: github.event_name == 'release'
307+ uses: svenstaro/upload-release-action@2.2.1
308+ with:
309+ repo_token: ${{ secrets.GITHUB_TOKEN }}
310+ file: compaREST-Windows.zip
311+ tag: ${{ github.ref }}
.github/workflows/haskell.ymldeleted−54
@@ -1,54 +0,0 @@
1−name: Haskell CI
2−
3−on:
4− push:
5− branches: [master]
6− pull_request:
7− branches: [master]
8−
9−jobs:
10− linux-build:
11− runs-on: ubuntu-latest
12−
13− steps:
14− - uses: actions/checkout@v2
15− with:
16− submodules: recursive
17− - name: Cache
18− uses: actions/cache@v1
19− with:
20− path: ~/.stack
21− key: ${{ runner.os }}-build-stack-${{ hashFiles('**/stack.yaml') }}-${{ hashFiles('**/*.cabal') }}
22− restore-keys: |
23− ${{ runner.os }}-build-stack-${{ hashFiles('**/stack.yaml') }}
24− ${{ runner.os }}-build-stack-
25− - name: Increase swap
26− run: |
27− free -h
28−
29− sudo swapoff /mnt/swapfile
30− sudo fallocate -l 12G /mnt/swapfile
31− sudo mkswap /mnt/swapfile
32− sudo swapon /mnt/swapfile
33−
34− free -h
35−
36− - name: Build
37− run: |
38− stack build --ghc-options=-O3 --local-bin-path openapi-diff-linux --copy-bins --flag openapi-diff:Static
39− - name: Test
40− run: stack test --ghc-options=-O3 --flag openapi-diff:Static --test-arguments='--hide-successes --no-create'
41−
42− - name: Zip
43− if: github.ref == 'refs/heads/master'
44− run: |
45− zip openapi-diff-linux openapi-diff-linux/openapi-diff
46− - uses: "marvinpinto/action-automatic-releases@latest"
47− if: github.ref == 'refs/heads/master'
48− with:
49− repo_token: "${{ secrets.GITHUB_TOKEN }}"
50− automatic_release_tag: "latest"
51− prerelease: false
52− title: "Pre-Release"
53− files: |
54− *.zip
.github/workflows/remove-artifacts.yamldeleted−17
@@ -1,17 +0,0 @@
1−name: Remove old artifacts
2−
3−on:
4− schedule:
5− - cron: '0 0 * * *'
6−jobs:
7− remove-old-artifacts:
8− runs-on: ubuntu-latest
9− timeout-minutes: 10
10−
11− steps:
12− - name: Remove old artifacts
13− uses: c-hive/gha-remove-artifacts@v1
14− with:
15− age: '7 days'
16− skip-tags: true
17− skip-recent: 10
CHANGELOG.md+1−1
@@ -1,6 +1,6 @@
11# Changelog
22
3−`openapi-diff` uses [PVP Versioning][1].
3+`comparest` uses [PVP Versioning][1].
44
55## 0.0.0.0
66
README.md+245−13
@@ -1,40 +1,272 @@
1−# openapi-diff
1+# CompaREST
22
3−[![Hackage](https://img.shields.io/hackage/v/openapi-diff.svg?logo=haskell)](https://hackage.haskell.org/package/openapi-diff)
4−[![Stackage Lts](http://stackage.org/package/openapi-diff/badge/lts)](http://stackage.org/lts/package/openapi-diff)
5−[![Stackage Nightly](http://stackage.org/package/openapi-diff/badge/nightly)](http://stackage.org/nightly/package/openapi-diff)
3+[![Hackage](https://img.shields.io/hackage/v/comparest.svg?logo=haskell)](https://hackage.haskell.org/package/comparest)
4+[![Stackage Lts](http://stackage.org/package/comparest/badge/lts)](http://stackage.org/lts/package/comparest)
5+[![Stackage Nightly](http://stackage.org/package/comparest/badge/nightly)](http://stackage.org/nightly/package/comparest)
66[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
77
88Compatibility checker for OpenAPI
9+# Quick Start Guide
910
11+## Your situation
12+
13+You are developing a very important server with a REST API. You have clients who use your API that you do not control. Say, you are also developing a mobile app that uses your API and you can't force someone to update to the latest version. (Or you prefer not to for UX reasons.)
14+
15+You have recently released version 1.0 and things are going great: user are downloading your app, servers are processing requests.
16+
17+You describe your API in a file `api-1.0.0.yaml`:
18+
19+```yaml
20+openapi: "3.0.0"
21+info:
22+ version: 1.0.0
23+ title: Swagger Petstore
24+ license:
25+ name: MIT
26+servers:
27+ - url: https://example.com
28+paths:
29+ /pets:
30+ get:
31+ parameters:
32+ - name: limit
33+ in: query
34+ required: false
35+ schema:
36+ type: integer
37+ maximum: 20
38+ responses:
39+ "200":
40+ description: ""
41+ headers:
42+ x-next:
43+ schema:
44+ type: string
45+ content:
46+ application/json:
47+ schema:
48+ $ref: "#/components/schemas/Pets"
49+ post:
50+ requestBody:
51+ content:
52+ application/json:
53+ schema:
54+ $ref: "#/components/schemas/Pet"
55+ responses:
56+ "201":
57+ description: ""
58+components:
59+ schemas:
60+ Pet:
61+ type: object
62+ required:
63+ - id
64+ - name
65+ properties:
66+ id:
67+ type: integer
68+ name:
69+ type: string
70+ minLength: 3
71+ maxLength: 10
72+ Pets:
73+ type: array
74+ items:
75+ $ref: "#/components/schemas/Pet"
76+```
77+
78+## Evolving your product
79+
80+Enthused over your initial success you hurry to release a new and improved version of your API and mobile app.
81+
82+After a round of very intense programming you take a look at your new `api-1.1.0.yaml`:
83+
84+```yaml
85+openapi: "3.0.0"
86+info:
87+ version: 1.1.0
88+ title: Swagger Petstore
89+ license:
90+ name: MIT
91+servers:
92+ - url: https://example.com
93+paths:
94+ /pets:
95+ get:
96+ parameters:
97+ - name: limit
98+ in: query
99+ required: false
100+ schema:
101+ type: integer
102+ maximum: 30
103+ responses:
104+ "200":
105+ description: ""
106+ headers:
107+ x-next:
108+ schema:
109+ type: string
110+ content:
111+ application/json:
112+ schema:
113+ $ref: "#/components/schemas/Pets"
114+ post:
115+ requestBody:
116+ content:
117+ application/json:
118+ schema:
119+ $ref: "#/components/schemas/Pet"
120+ responses:
121+ "201":
122+ description: ""
123+components:
124+ schemas:
125+ Pet:
126+ type: object
127+ required:
128+ - id
129+ - name
130+ properties:
131+ id:
132+ type: integer
133+ name:
134+ type: string
135+ minLength: 1
136+ maxLength: 15
137+ weight:
138+ type: integer
139+ Pets:
140+ type: array
141+ items:
142+ $ref: "#/components/schemas/Pet"
143+```
144+
145+Looking at the very large and complex API description, you grow more and more concerned that your old mobile app might stop working when you update the server. But the spec is too large and too complex to reasonably assess this manually.
146+
147+## Assessing compatibility automatically
148+
149+Luckily, you have access to compaREST which can programmatically analyze your APIs and determine what, if anything, breaks compatibility and what doesn't.
150+
151+You can call it, passing the API your client will be aware of, and the API your server will serve like so:
152+
153+```bash
154+docker run --rm -v $(pwd):/data:rw typeable/comparest --client /data/api-1.0.0.yaml --server /data/api-1.1.0.yaml --output /data/report.md
155+```
156+
157+Running this command will output a file `report.md`, containing the compatibility report between the two APIs:
158+
159+> # Summary
160+>
161+> | [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
162+> |------------------------------------------|-------------------------------------------------|-------------------------------|
163+> | 5 | 6 | 0 |
164+>
165+> # <span id="breaking-changes"></span>⚠️ Breaking changes
166+>
167+> ## **GET** /pets
168+>
169+> ### 📱⬅️ JSON Response – 200
170+>
171+> #### `$[*].name(String)`
172+>
173+> 1. Maximum length of the string changed from 10 to 15.
174+>
175+> 2. Minimum length of the string changed from 3 to 1.
176+>
177+> ## **POST** /pets
178+>
179+> ### 📱➡️ JSON Request
180+>
181+> #### `$.weight`
182+>
183+> 1. Values are now limited to the following types:
184+>
185+> - Number
186+>
187+> 2. The property was previously implicitly described by the catch-all
188+> "additional properties" case. It is now explicitly defined.
189+>
190+> #### `$.weight(Number)`
191+>
192+> Value is now a multiple of 1.0.
193+>
194+> # <span id="non-breaking-changes"></span>🙆 Non-breaking changes
195+>
196+> ## **GET** /pets
197+>
198+> ### Parameter limit
199+>
200+> #### JSON Schema
201+>
202+> ##### `$(Number)`
203+>
204+> Upper bound changed from 20.0 inclusive to 30.0 inclusive.
205+>
206+> ### 📱⬅️ JSON Response – 200
207+>
208+> #### `$[*].weight`
209+>
210+> 1. Values are now limited to the following types:
211+>
212+> - Number
213+>
214+> 2. The property was previously implicitly described by the catch-all
215+> "additional properties" case. It is now explicitly defined.
216+>
217+> #### `$[*].weight(Number)`
218+>
219+> Value is now a multiple of 1.0.
220+>
221+> ## **POST** /pets
222+>
223+> ### 📱➡️ JSON Request
224+>
225+> #### `$.name(String)`
226+>
227+> 1. Maximum length of the string changed from 10 to 15.
228+>
229+> 2. Minimum length of the string changed from 3 to 1.
230+
231+You now know exactly in what situations and in what way your 1.0 version of the app will break if you deploy your 1.1 version of the server.
232+
233+## Additional formats
234+
235+You can also produce a self-contained HTML report that you can open in your browser by simply omitting the file extension of the output file:
236+
237+```bash
238+docker run --rm -v $(pwd):/data:rw typeable/comparest --client /data/api-1.0.0.yaml --server /data/api-1.1.0.yaml --output /data/report
239+```
240+
241+# CLI docs
10242
11243```
12−Usage: openapi-diff (-c|--client ARG) (-s|--server ARG)
13− [--silent | --only-breaking | --all] [-o|--output ARG]
14− [--folding-block-quotes-style | --header-style]
15− [--signal-exit-code]
16− A tool to check compatibility between two OpenApi specifications.
244+Usage: comparest (-c|--client ARG) (-s|--server ARG)
245+ [--silent | --only-breaking | --all] [-o|--output ARG]
246+ [--folding-block-quotes-style | --header-style]
247+ [--signal-exit-code]
248+ A tool to check compatibility between two OpenAPI specifications.
17249
18250 Usage examples
19251
20252 Compare files old.yaml with new.yaml and output the resulting report to
21253 stdout:
22254
23− openapi-diff -c old.yaml -s new.yaml
255+ comparest -c old.yaml -s new.yaml
24256
25257 Only output breaking changes and write a styled HTML report to file
26258 report.html:
27259
28− openapi-diff -c old.yaml -s new.yaml --only-breaking -o report
260+ comparest -c old.yaml -s new.yaml --only-breaking -o report
29261
30262 Don't output anything, only fail if there are breaking changes:
31263
32− openapi-diff -c old.json -s new.json --silent
264+ comparest -c old.json -s new.json --silent
33265
34266 Write full report suitable for embedding into a GitHub comment to
35267 report.html:
36268
37− openapi-diff -c old.json -s new.json --folding-block-quotes-style -o report.html
269+ comparest -c old.json -s new.json --folding-block-quotes-style -o report.html
38270
39271Available options:
40272 -h,--help Show this help text
action.yamlnew file+47
@@ -0,0 +1,47 @@
1+name: compaREST
2+author: Typeable
3+description: API reliability checker
4+inputs:
5+ GITHUB_TOKEN:
6+ description: "The GitHub access token (e.g. secrets.GITHUB_TOKEN) used to create or update the comment. This defaults to {{ github.token }}."
7+ default: "${{ github.token }}"
8+ required: false
9+ repo:
10+ description: The owner of the repo in which to post the comment.
11+ default: "${{ github.repository }}"
12+ required: false
13+ project_name:
14+ description: The name of the project to which the API pertains.
15+ required: true
16+ footer:
17+ description: A footer that can be appended to the comment.
18+ required: false
19+ default: ""
20+ old:
21+ description: The path to old specification of the API.
22+ required: true
23+ new:
24+ description: The path to new specification of the API.
25+ required: true
26+ sha:
27+ description: The sha of the commit to post the check for.
28+ required: false
29+ default: "${{ github.event.pull_request.head.sha }}"
30+runs:
31+ using: "docker"
32+ image: "docker://typeable/comparest-github-action:0.0.0.0"
33+ env:
34+ GITHUB_TOKEN: "${{ inputs.GITHUB_TOKEN }}"
35+ REPO: "${{ inputs.repo }}"
36+ PROJECT_NAME: "${{ inputs.project_name }}"
37+ FOOTER: "${{ inputs.footer }}"
38+ ROOT: "/github/workspace"
39+ OLD: "${{ inputs.old }}"
40+ NEW: "${{ inputs.new }}"
41+ SHA: "${{ inputs.sha }}"
42+ pre-entrypoint: "/bin/pre"
43+ entrypoint: "/bin/run"
44+
45+branding:
46+ icon: crosshair
47+ color: gray-dark
app/Data/OpenApi/Compare/Options.hsnew file+134
@@ -0,0 +1,134 @@
1+module Data.OpenApi.Compare.Options
2+ ( Options (..),
3+ OutputMode (..),
4+ parseOptions,
5+ )
6+where
7+
8+import Data.OpenApi.Compare.Report
9+import GHC.Generics (Generic)
10+import Options.Applicative
11+import Options.Applicative.Help hiding (fullDesc)
12+
13+parseOptions :: IO Options
14+parseOptions = customExecParser (prefs $ showHelpOnError) optionsParserInfo
15+
16+data Options = Options
17+ { clientFile :: FilePath
18+ , serverFile :: FilePath
19+ , -- | 'Nothing' means "don't produce any output"
20+ mode :: Maybe ReportMode
21+ , outputMode :: OutputMode
22+ , reportTreeStyle :: ReportTreeStyle
23+ , signalExitCode :: Bool
24+ }
25+ deriving stock (Generic)
26+
27+data OutputMode = StdoutMode | FileMode FilePath
28+
29+optionsParserInfo :: ParserInfo Options
30+optionsParserInfo =
31+ info
32+ (helper <*> optionsParser)
33+ ( fullDesc
34+ <> header "CompaREST"
35+ <> progDescDoc
36+ ( Just $
37+ par "A tool to check compatibility between two OpenAPI specifications."
38+ <$$> hardline <> par "Usage examples" <> hardline
39+ <$$> indent
40+ 4
41+ ( par "Compare files old.yaml with new.yaml and output the resulting report to stdout:"
42+ <$$> hardline <> indent 4 "comparest -c old.yaml -s new.yaml"
43+ <$$> hardline <> par "Only output breaking changes and write a styled HTML report to file report.html:"
44+ <$$> hardline <> indent 4 "comparest -c old.yaml -s new.yaml --only-breaking -o report"
45+ <$$> hardline <> par "Don't output anything, only fail if there are breaking changes:"
46+ <$$> hardline <> indent 4 "comparest -c old.json -s new.json --silent"
47+ <$$> hardline <> par "Write full report suitable for embedding into a GitHub comment to report.html:"
48+ <$$> hardline <> indent 4 "comparest -c old.json -s new.json --folding-block-quotes-style -o report.html"
49+ )
50+ )
51+ )
52+
53+optionsParser :: Parser Options
54+optionsParser =
55+ Options
56+ <$> strOption
57+ ( short 'c'
58+ <> long "client"
59+ <> help
60+ "A path to the file containing the specification that will be \
61+ \used for the client of the API. Can be either a YAML or JSON file."
62+ )
63+ <*> strOption
64+ ( short 's'
65+ <> long "server"
66+ <> help
67+ "A path to the file containing the specification that will be \
68+ \used for the server of the API. Can be either a YAML or JSON file."
69+ )
70+ <*> ( flag'
71+ Nothing
72+ ( long "silent"
73+ <> help "Silence all output. Only makes sense in combination with --signal-exit-code."
74+ )
75+ <|> flag'
76+ (Just OnlyErrors)
77+ ( long "only-breaking"
78+ <> help "Only report breaking changes in the output."
79+ )
80+ <|> flag'
81+ (Just All)
82+ ( long "all"
83+ <> help
84+ "Report both incompatible and compatible changes. \
85+ \Compatible changes will not trigger a failure exit code."
86+ )
87+ <|> pure (Just All)
88+ )
89+ <*> ( ( FileMode
90+ <$> strOption
91+ ( short 'o' <> long "output"
92+ <> helpDoc
93+ ( Just $
94+ par "The file path where the output should be written. If the option is omitted the result will be written to stdout."
95+ <$$> hardline <> par "The file extension is used to determine the type of the output file."
96+ <$$> hardline <> par "Supports many formats such as markdown, html, rtf, doc, txt, rst, and many more."
97+ <$$> hardline <> par "Leave out the extension to produce a self-contained HTML report with styling."
98+ )
99+ )
100+ )
101+ <|> pure StdoutMode
102+ )
103+ <*> ( flag'
104+ FoldingBlockquotesTreeStyle
105+ ( long "folding-block-quotes-style"
106+ <> help
107+ "The report tree is structured using \
108+ \summary/detail HTML elements and indented using \
109+ \block quotes. This style renders well on GitHub.\
110+ \Intended for HTML output format. Markdown has rendering \
111+ \bugs on GitHub."
112+ )
113+ <|> flag'
114+ HeadersTreeStyle
115+ ( long "header-style"
116+ <> help
117+ "The report tree is structured using \
118+ \increasing levels of headers."
119+ )
120+ <|> pure HeadersTreeStyle
121+ )
122+ <*> switch
123+ ( long "signal-exit-code"
124+ <> helpDoc
125+ ( Just $
126+ par "Signal API compatibility with the exit code."
127+ <$$> hardline <> par "Exit with 0 if there are no breaking changes."
128+ <$$> par "Exit with 1 if there are breaking changes."
129+ <$$> par "Exit with 2 if could not determine compatibility."
130+ )
131+ )
132+
133+par :: String -> Doc
134+par = foldr1 (</>) . fmap string . words
app/FormatHeuristic.hs+3−3
@@ -2,7 +2,7 @@
22-- Originally based on:
33-- https://github.com/jgm/pandoc/blob/master/src/Text/Pandoc/App/FormatHeuristics.hs
44module FormatHeuristic
5− ( formatFromFilePath
5+ ( formatFromFilePath,
66 )
77where
88
@@ -10,10 +10,10 @@ import Data.ByteString.Lazy (ByteString)
1010import Data.Char (toLower)
1111import Data.Functor
1212import qualified Data.Map as M
13+import qualified Data.OpenApi.Compare.Report.Html.Template as Html
1314import Data.Text (Text)
1415import qualified Data.Text.Lazy as TL
1516import qualified Data.Text.Lazy.Encoding as TL
16−import qualified OpenAPI.Checker.Report.Html.Template as Html
1717import System.FilePath (takeExtension)
1818import Text.DocTemplates.Internal
1919import Text.Pandoc
@@ -80,7 +80,7 @@ formatFromFilePath x =
8080 def
8181 { writerTemplate = Just Html.template
8282 , -- Not actually used. Needed to silence warning.
83− writerVariables = Context $ M.fromList [("pagetitle", toVal ("OpenApi Diff" :: Text))]
83+ writerVariables = Context $ M.fromList [("pagetitle", toVal ("CompaREST" :: Text))]
8484 }
8585 _ -> Nothing
8686 where
app/Main.hs+4−3
@@ -6,14 +6,15 @@ import Data.Aeson
66import qualified Data.ByteString.Lazy as BSL
77import Data.Default
88import Data.Maybe
9+import Data.OpenApi.Compare.Options
10+import Data.OpenApi.Compare.Run
911import qualified Data.Text.IO as T
1012import qualified Data.Yaml as Yaml
1113import FormatHeuristic
12−import OpenAPI.Checker.Options
13−import OpenAPI.Checker.Run
1414import System.Exit
1515import System.IO
1616import Text.Pandoc hiding (report)
17+import Text.Pandoc.Builder
1718
1819main :: IO ()
1920main = do
@@ -47,7 +48,7 @@ main = do
4748 }
4849 (report, status) = runReport reportConfig (a, b)
4950 case mode opts of
50− Just _ -> either handler pure <=< runExceptT $ write report
51+ Just _ -> either handler pure <=< runExceptT $ write $ doc report
5152 Nothing -> pure ()
5253 when (signalExitCode opts) $
5354 case status of
app/OpenAPI/Checker/Options.hsdeleted−115
@@ -1,115 +0,0 @@
1−module OpenAPI.Checker.Options
2− ( Options (..)
3− , OutputMode (..)
4− , parseOptions
5− )
6−where
7−
8−import GHC.Generics (Generic)
9−import OpenAPI.Checker.Report
10−import Options.Applicative
11−import Options.Applicative.Help hiding (fullDesc)
12−
13−parseOptions :: IO Options
14−parseOptions = customExecParser (prefs $ showHelpOnError) optionsParserInfo
15−
16−data Options = Options
17− { clientFile :: FilePath
18− , serverFile :: FilePath
19− , -- | 'Nothing' means "don't produce any output"
20− mode :: Maybe ReportMode
21− , outputMode :: OutputMode
22− , reportTreeStyle :: ReportTreeStyle
23− , signalExitCode :: Bool
24− }
25− deriving stock (Generic)
26−
27−data OutputMode = StdoutMode | FileMode FilePath
28−
29−optionsParserInfo :: ParserInfo Options
30−optionsParserInfo =
31− info
32− (helper <*> optionsParser)
33− (fullDesc
34− <> header "openapi-diff"
35− <> progDescDoc
36− (Just $
37− par "A tool to check compatibility between two OpenApi specifications."
38− <$$> hardline <> par "Usage examples" <> hardline
39− <$$> indent
40− 4
41− (par "Compare files old.yaml with new.yaml and output the resulting report to stdout:"
42− <$$> hardline <> indent 4 "openapi-diff -c old.yaml -s new.yaml"
43− <$$> hardline <> par "Only output breaking changes and write a styled HTML report to file report.html:"
44− <$$> hardline <> indent 4 "openapi-diff -c old.yaml -s new.yaml --only-breaking -o report"
45− <$$> hardline <> par "Don't output anything, only fail if there are breaking changes:"
46− <$$> hardline <> indent 4 "openapi-diff -c old.json -s new.json --silent"
47− <$$> hardline <> par "Write full report suitable for embedding into a GitHub comment to report.html:"
48− <$$> hardline <> indent 4 "openapi-diff -c old.json -s new.json --folding-block-quotes-style -o report.html")))
49−
50−optionsParser :: Parser Options
51−optionsParser =
52− Options
53− <$> strOption
54− (short 'c'
55− <> long "client"
56− <> help
57− "A path to the file containing the specification that will be \
58− \used for the client of the API. Can be either a YAML or JSON file.")
59− <*> strOption
60− (short 's'
61− <> long "server"
62− <> help
63− "A path to the file containing the specification that will be \
64− \used for the server of the API. Can be either a YAML or JSON file.")
65− <*> (flag'
66− Nothing
67− (long "silent"
68− <> help "Silence all output. Only makes sense in combination with --signal-exit-code.")
69− <|> flag'
70− (Just OnlyErrors)
71− (long "only-breaking"
72− <> help "Only report breaking changes in the output.")
73− <|> flag'
74− (Just All)
75− (long "all"
76− <> help
77− "Report both incompatible and compatible changes. \
78− \Compatible changes will not trigger a failure exit code.")
79− <|> pure (Just All))
80− <*> ((FileMode
81− <$> strOption
82− (short 'o' <> long "output"
83− <> helpDoc
84− (Just $
85− par "The file path where the output should be written. If the option is omitted the result will be written to stdout."
86− <$$> hardline <> par "The file extension is used to determine the type of the output file."
87− <$$> hardline <> par "Supports many formats such as markdown, html, rtf, doc, txt, rst, and many more."
88− <$$> hardline <> par "Leave out the extension to produce a self-contained HTML report with styling.")))
89− <|> pure StdoutMode)
90− <*> (flag'
91− FoldingBlockquotesTreeStyle
92− (long "folding-block-quotes-style"
93− <> help
94− "The report tree is structured using \
95− \summary/detail HTML elements and indented using \
96− \block quotes. This style renders well on GitHub.\
97− \Intended for HTML output format. Markdown has rendering \
98− \bugs on GitHub.")
99− <|> flag'
100− HeadersTreeStyle
101− (long "header-style"
102− <> help
103− "The report tree is structured using \
104− \increasing levels of headers.")
105− <|> pure HeadersTreeStyle)
106− <*> switch
107− (long "signal-exit-code"
108− <> helpDoc (Just $
109− par "Signal API compatibility with the exit code."
110− <$$> hardline <> par "Exit with 0 if there are no breaking changes."
111− <$$> par "Exit with 1 if there are breaking changes."
112− <$$> par "Exit with 2 if could not determine compatibility."))
113−
114−par :: String -> Doc
115−par = foldr1 (</>) . fmap string . words
benchmarks/README.md+2−2
@@ -1,6 +1,6 @@
11# OpenApi comparison benchmarks
22
3−This is a nix expression that automatically downloads, builds and runs multiple OpenApi comparison tools on the same set of inputs to compare how well they behave.
3+This is a nix expression that automatically downloads, builds and runs multiple OpenAPI comparison tools on the same set of inputs to compare how well they behave.
44
55To run the benchmarks run the following command in this directory:
66
@@ -12,7 +12,7 @@ nix-build
1212
1313| Tool | Output file name |
1414| -------------------------------------------------------------------------- | ------------------------------------------- |
15−| [Typeable OpenApi Diff](https://github.com/typeable/openapi-diff) | `typeable.md` |
15+| [Typeable CompaREST](https://github.com/typeable/compaREST) | `typeable.md` |
1616| [Atlassian OpenApi Diff](https://bitbucket.org/atlassian/openapi-diff) | `atlassian.json`, `atlassian.error.txt` |
1717| [OpenApi Tools OpenApi Diff](https://github.com/OpenAPITools/openapi-diff) | `openapitools.md`, `openapitools.error.txt` |
1818| [Tufin OpenApi Diff](https://github.com/tufin/oasdiff) | `oasdiff.txt`, `oasdiff.error.txt` |
benchmarks/default.nix+8−12
@@ -1,6 +1,6 @@
11{ sources ? import ./nix/sources.nix
2−, haskellNix ? import sources.haskellNix { }
32, gomod2nix ? sources.gomod2nix
3+, haskellNix ? (import ../. {}).haskellNix
44, pkgs ? import haskellNix.sources.nixpkgs-2105 (haskellNix.nixpkgsArgs // {
55 overlays = haskellNix.nixpkgsArgs.overlays ++ [
66 (self: super: {
@@ -8,23 +8,19 @@
88 })
99 ];
1010 })
11+, compaRESThsPkgs ? (import ../. {}).hsPkgs
1112, npmNix ? import (sources.npmNix + "/npmPackages") { inherit pkgs; }
1213, mavenix ? import (sources.mavenix) { inherit pkgs; }
1314, bumpToken ? null
1415, bumpDocumentation ? null
1516}:
1617let
17− typeable-openapi-diff = (pkgs.haskell-nix.stackProject {
18− src = pkgs.haskell-nix.haskellLib.cleanGit {
19− name = "openapi-diff";
20− src = ./..;
21− };
22− }).openapi-diff.components.exes.openapi-diff;
23− typeable-openapi-diff-exe = typeable-openapi-diff + "/bin/openapi-diff";
24− typeable-openapi-diff-differ = pkgs.writeScript "typeable-openapi-diff-differ" ''
18+ typeable-comparest = compaRESThsPkgs.comparest.components.exes.comparest;
19+ typeable-comparest-exe = typeable-comparest + "/bin/comparest";
20+ typeable-comparest-differ = pkgs.writeScript "typeable-comparest-differ" ''
2521 #!${pkgs.stdenv.shell}
2622 set +e
27− ${typeable-openapi-diff-exe} -c $1 -s $2 -o $3/typeable.md
23+ ${typeable-comparest-exe} -c $1 -s $2 -o $3/typeable.md
2824 exit 0
2925 '';
3026
@@ -130,7 +126,7 @@ let
130126 })
131127 (getInputs root);
132128 differs = [
133− typeable-openapi-diff-differ
129+ typeable-comparest-differ
134130 atlassian-openapi-diff-differ
135131 openapitools-openapi-diff-differ
136132 oasdiff-differ
@@ -164,7 +160,7 @@ let
164160 tools = [
165161 "Typeable"
166162 "Atlassian"
167− "OpenApi Tools"
163+ "OpenAPI Tools"
168164 "oasdiff"
169165 "Bump.sh"
170166 ];
ci/policeman.nixnew file+4
@@ -0,0 +1,4 @@
1+{ sources ? import ../nix/sources.nix
2+, pkgs ? import sources.nixpkgs { }
3+}:
4+pkgs.haskellPackages.policeman
openapi-diff.cabal → compaREST.cabalrenamed+83−56
@@ -1,29 +1,24 @@
11cabal-version: 2.4
2−name: openapi-diff
2+name: compaREST
33version: 0.0.0.0
44synopsis: Compatibility checker for OpenAPI
5−description: Compatibility checker for OpenAPI
5+description: Compatibility checker for OpenAPI.
66license: MIT
77license-file: LICENSE
8−author: Aleksey Uymanov
9−maintainer: Aleksey Uymanov <s9gf4ult@gmail.com>
10−copyright: 2021 Aleksey Uymanov
8+author: Typeable
9+maintainer: compaREST@typeable.io
10+copyright: 2021 Typeable
1111category: Web
1212build-type: Simple
1313extra-doc-files: README.md
1414 CHANGELOG.md
1515tested-with: GHC == 8.10.4
1616extra-source-files: awsm-css/dist/awsm.min.css
17−
18−Flag static
19− Default: False
20− Manual: True
17+data-files:
18+ test/golden/**/*.yaml
19+ test/golden/**/*.md
2120
2221common common-options
23− if flag(static)
24− cc-options: -static
25− ld-options: -static -pthread
26− ghc-options: -static
2722 ghc-options:
2823 -Weverything
2924 -Wno-implicit-prelude
@@ -38,9 +33,9 @@ common common-options
3833 -fconstraint-solver-iterations=0
3934 default-language: Haskell2010
4035 build-depends: base >= 4.12.0.0 && < 4.16
41− , bytestring
4236 , text
4337
38+
4439 default-extensions: ApplicativeDo
4540 , BangPatterns
4641 , ConstraintKinds
@@ -82,6 +77,7 @@ common common-options
8277 , QuantifiedConstraints
8378 , DefaultSignatures
8479
80+
8581library
8682 import: common-options
8783 build-depends:
@@ -111,52 +107,53 @@ library
111107 , file-embed
112108 , data-default
113109 , ordered-containers
110+ , bytestring
114111 hs-source-dirs: src
115− exposed-modules: Data.HList
116− , OpenAPI.Checker.Behavior
117− , OpenAPI.Checker.Formula
118− , OpenAPI.Checker.Memo
119− , OpenAPI.Checker.Orphans
120− , OpenAPI.Checker.Paths
121− , OpenAPI.Checker.PathsPrefixTree
122− , OpenAPI.Checker.References
123− , OpenAPI.Checker.Subtree
124− , OpenAPI.Checker.Validate.MediaTypeObject
125− , OpenAPI.Checker.Validate.OpenApi
126− , OpenAPI.Checker.Validate.Operation
127− , OpenAPI.Checker.Validate.Param
128− , OpenAPI.Checker.Validate.PathFragment
129− , OpenAPI.Checker.Validate.Products
130− , OpenAPI.Checker.Validate.RequestBody
131− , OpenAPI.Checker.Validate.Responses
132− , OpenAPI.Checker.Validate.Schema
133− , OpenAPI.Checker.Validate.Schema.TypedJson
134− , OpenAPI.Checker.Validate.Schema.DNF
135− , OpenAPI.Checker.Validate.Schema.Issues
136− , OpenAPI.Checker.Validate.Schema.JsonFormula
137− , OpenAPI.Checker.Validate.Schema.Partition
138− , OpenAPI.Checker.Validate.Schema.Process
139− , OpenAPI.Checker.Validate.Schema.Traced
140− , OpenAPI.Checker.Validate.SecurityRequirement
141− , OpenAPI.Checker.Validate.Server
142− , OpenAPI.Checker.Validate.Sums
143− , OpenAPI.Checker.Validate.Header
144− , OpenAPI.Checker.Validate.Link
145− , OpenAPI.Checker.Common
146− , OpenAPI.Checker.Validate.SecurityScheme
147− , OpenAPI.Checker.Validate.OAuth2Flows
148− , OpenAPI.Checker.Report
112+ exposed-modules: Data.OpenApi.Compare.Behavior
113+ , Data.OpenApi.Compare.Common
114+ , Data.OpenApi.Compare.Formula
115+ , Data.OpenApi.Compare.Memo
116+ , Data.OpenApi.Compare.Orphans
117+ , Data.OpenApi.Compare.Paths
118+ , Data.OpenApi.Compare.PathsPrefixTree
119+ , Data.OpenApi.Compare.References
120+ , Data.OpenApi.Compare.Report
121+ , Data.OpenApi.Compare.Report.Html.Template
122+ , Data.OpenApi.Compare.Report.Jet
123+ , Data.OpenApi.Compare.Run
124+ , Data.OpenApi.Compare.Subtree
125+ , Data.OpenApi.Compare.Validate.Header
126+ , Data.OpenApi.Compare.Validate.Link
127+ , Data.OpenApi.Compare.Validate.MediaTypeObject
128+ , Data.OpenApi.Compare.Validate.OAuth2Flows
129+ , Data.OpenApi.Compare.Validate.OpenApi
130+ , Data.OpenApi.Compare.Validate.Operation
131+ , Data.OpenApi.Compare.Validate.Param
132+ , Data.OpenApi.Compare.Validate.PathFragment
133+ , Data.OpenApi.Compare.Validate.Products
134+ , Data.OpenApi.Compare.Validate.RequestBody
135+ , Data.OpenApi.Compare.Validate.Responses
136+ , Data.OpenApi.Compare.Validate.Schema
137+ , Data.OpenApi.Compare.Validate.Schema.DNF
138+ , Data.OpenApi.Compare.Validate.Schema.Issues
139+ , Data.OpenApi.Compare.Validate.Schema.JsonFormula
140+ , Data.OpenApi.Compare.Validate.Schema.Partition
141+ , Data.OpenApi.Compare.Validate.Schema.Process
142+ , Data.OpenApi.Compare.Validate.Schema.Traced
143+ , Data.OpenApi.Compare.Validate.Schema.TypedJson
144+ , Data.OpenApi.Compare.Validate.SecurityRequirement
145+ , Data.OpenApi.Compare.Validate.SecurityScheme
146+ , Data.OpenApi.Compare.Validate.Server
147+ , Data.OpenApi.Compare.Validate.Sums
148+ other-modules: Data.HList
149149 , Data.OpenUnion.Extra
150− , OpenAPI.Checker.Report.Jet
151− , OpenAPI.Checker.Run
152− , OpenAPI.Checker.Report.Html.Template
153150
154−executable openapi-diff
151+executable compaREST
155152 import: common-options
156153 hs-source-dirs: app
157154 main-is: Main.hs
158155 build-depends:
159− openapi-diff
156+ compaREST
160157 , pandoc
161158 , data-default
162159 , bytestring
@@ -167,14 +164,41 @@ executable openapi-diff
167164 , aeson
168165 , containers
169166 , doctemplates
167+ , pandoc-types
170168 ghc-options: -threaded
171169 -rtsopts
172170 -with-rtsopts=-N
173171 other-modules:
174172 FormatHeuristic
175− OpenAPI.Checker.Options
173+ Data.OpenApi.Compare.Options
174+
175+executable compaREST-GitHub-Action
176+ import: common-options
177+ hs-source-dirs: github-action
178+ main-is: Main.hs
179+ build-depends:
180+ compaREST
181+ , pandoc
182+ , yaml
183+ , aeson
184+ , github
185+ , freer-simple
186+ , vector
187+ , pandoc-types
188+ , envy
189+ , filepath
190+ , bytestring
191+ ghc-options: -threaded
192+ -rtsopts
193+ -with-rtsopts=-N
194+ other-modules:
195+ Control.Monad.Freer.GitHub
196+ CompaREST.GitHub.API
197+ CompaREST.GitHub.Action.Config
198+ GitHub.Data.Checks
199+ GitHub.Endpoints.Checks
176200
177−test-suite openapi-diff-test
201+test-suite compaREST-tests
178202 import: common-options
179203 type: exitcode-stdio-1.0
180204 hs-source-dirs: test
@@ -182,8 +206,10 @@ test-suite openapi-diff-test
182206 other-modules:
183207 Spec.Golden.Extra
184208 Spec.Golden.TraceTree
209+ Paths_compaREST
210+ autogen-modules: Paths_compaREST
185211 build-depends:
186− , openapi-diff
212+ , compaREST
187213 , tasty-golden
188214 , tasty
189215 , bytestring
@@ -193,6 +219,7 @@ test-suite openapi-diff-test
193219 , pandoc
194220 , data-default
195221 , lens
222+ , pandoc-types
196223 ghc-options: -threaded
197224 -rtsopts
198225 -with-rtsopts=-N
default.nixnew file+114
@@ -0,0 +1,114 @@
1+{ sources ? import ./nix/sources.nix
2+, haskellNix ? import sources.haskellNix { inherit system; }
3+, pkgs ? import haskellNix.sources.nixpkgs-2105 (haskellNix.nixpkgsArgs // { inherit system; })
4+, system ? builtins.currentSystem
5+, nix-filter ? import sources.nix-filter
6+}:
7+let
8+ masterPkgs = import sources.nixpkgs { inherit system; };
9+
10+ hsPkgs = pkgs.haskell-nix.stackProject {
11+ src = nix-filter {
12+ root = ./.;
13+ name = "compaREST";
14+ include = [
15+ ./stack.yaml
16+ ./stack.yaml.lock
17+ ./compaREST.cabal
18+ ];
19+ };
20+
21+ modules = [
22+ {
23+ dontStrip = false;
24+ dontPatchELF = false;
25+ enableDeadCodeElimination = true;
26+ ghcOptions = [
27+ "-O2"
28+ "-fexpose-all-unfoldings"
29+ "-fspecialise-aggressively"
30+ ];
31+ packages.pandoc.ghcOptions = [ "-O1" ];
32+ packages.compaREST.src = nix-filter {
33+ root = ./.;
34+ name = "compaREST-src";
35+ include = with nix-filter; [
36+ (./compaREST.cabal)
37+ (inDirectory ./test)
38+ (inDirectory ./src)
39+ (inDirectory ./app)
40+ (inDirectory ./github-action)
41+ ./awsm-css/dist/awsm.min.css
42+ ./LICENSE
43+ ];
44+ };
45+ }
46+ ];
47+ };
48+
49+ staticify = name: drv: pkgs.runCommand name { } ''
50+ mkdir -p $out/bin
51+ cp ${drv + "/bin"}/* $out/bin
52+
53+ ${pkgs.nukeReferences}/bin/nuke-refs $out/bin/*
54+ '';
55+
56+ compaRESTBin = hsPkgs.compaREST.components.exes.compaREST;
57+ compaRESTStaticBin = (staticify "compaREST-static" hsPkgs.projectCross.musl64.hsPkgs.compaREST.components.exes.compaREST);
58+
59+
60+ # doesn't work
61+ armDarwinCompaREST = hsPkgs.projectCross.aarch64-darwin.hsPkgs.compaREST.components.exes.compaREST;
62+
63+ compaRESTImage = pkgs.dockerTools.buildImage {
64+ name = "compaREST";
65+ contents = [ compaRESTStaticBin ];
66+ config = {
67+ Entrypoint = [ "/bin/compaREST" ];
68+ };
69+ };
70+
71+ macOSCompaRESTBundle = pkgs.runCommand "compaREST-macOS-bundled"
72+ {
73+ buildInputs = [ masterPkgs.macdylibbundler ];
74+ } ''
75+ mkdir -p $out/lib
76+ cp ${compaRESTBin}/bin/compaREST $out/compaREST
77+ chmod 755 $out/compaREST
78+ dylibbundler -b \
79+ -x $out/compaREST \
80+ -d $out/lib \
81+ -p '@executable_path/lib'
82+ '';
83+
84+
85+ compaRESTGithubAction =
86+ let
87+ action = staticify "compaREST-GitHub-Action-static" hsPkgs.projectCross.musl64.hsPkgs.compaREST.components.exes.compaREST-GitHub-Action;
88+ wrapped = pkgs.runCommand "wrapped-compaREST-GitHub-Action" { buildInputs = [ pkgs.makeWrapper ]; } ''
89+ makeWrapper ${action}/bin/compaREST-GitHub-Action $out/bin/pre --add-flags "pre"
90+ makeWrapper ${action}/bin/compaREST-GitHub-Action $out/bin/run --add-flags "run"
91+ '';
92+ in
93+ pkgs.dockerTools.buildImage {
94+ name = "typeable/compaREST-GitHub-Action";
95+ tag = "latest";
96+ contents = [ wrapped pkgs.cacert ];
97+ };
98+
99+ WindowsCompaRESTBin = hsPkgs.projectCross.mingwW64.hsPkgs.compaREST.components.exes.compaREST;
100+in
101+{
102+ inherit
103+ compaRESTImage
104+ compaRESTGithubAction
105+ compaRESTStaticBin
106+ compaRESTBin
107+ hsPkgs
108+ macOSCompaRESTBundle
109+ WindowsCompaRESTBin;
110+
111+ # We use the static version so that we don't have to rebuild everything on CI.
112+ # The only binaries build on CI are static.
113+ test = hsPkgs.projectCross.musl64.hsPkgs.compaREST.components.tests.compaREST-tests;
114+}
docs/Development_guide.mdnew file+149
@@ -0,0 +1,149 @@
1+CompaREST
2+=========
3+The tool works by simultaneously looking at a pair of nodes in OpenAPI schema
4+trees. At every place we try to understand whether one side (the producer) can
5+produce something that the other side (the consumer) cannot consume. This often
6+involves recursively running the same check on child sub-nodes. The necessary
7+criteria are implemented manually for all types of nodes that appear in the
8+schema. The `Subtree` class in `Data.OpenApi.Compare.Subtree` designates types
9+of nodes for which this compatibility checking is defined. The implementations
10+for various types reside in `Data.OpenApi.Compare.Validate.*`.
11+
12+Producer/Consumer
13+-----------------
14+Rather than looking at which schema is the server's and which is the client's,
15+in terms of compatibility checking it is more useful to track which side is the
16+"producer" and which is the "consumer". At the root of the schema the producer
17+is the client, but as we descend into an HTTP response, the direction flips and
18+the producer becomes the server.
19+
20+We deal with a lot of things in pairs, where one thing belongs to the producer
21+and another belongs to the consumer. The `ProdCons` datatype (in
22+`Data.OpenApi.Compare.Subtree`) is just a pair type that provides an Applicative
23+abstraction for working with two things simultaneously.
24+
25+Trees
26+-----
27+We directly use the datatypes defined in the `openapi3` library. For reasons of
28+identifiability and memoization we tag all nodes in the trees with a path from
29+the root. The tree is heterogeneous (nodes have different types), and every type
30+of node has a different arrangement of children. There is a `Step` data family
31+that defines all the possible children (of a specific type) of a node (of a
32+specific type).
33+
34+A `Trace` is a sequence of steps, except in a path the types of adjacent steps
35+have to agree. The `Paths` datatype achieves this. Together all paths form a
36+`Category`, and we often concatenate them using `>>>`. This is defined in
37+`Data.OpenApi.Compare.Paths`.
38+
39+We often keep a node and a path to it next to each other using an environment
40+comonad. `Traced` is a type alias around `Env` which ensures that the type of
41+the path matches the type of the node.
42+
43+Issues
44+------
45+The output of the tool is a list of compatibility issues that came up during
46+checking. The `Issue` data family describes the kinds of issues that can occur
47+at each type of node. An issue is supposed to characterize a set of interactions
48+(requests or responses) that demonstrate that the schemas are incompatible.
49+
50+Each issue is tagged with a `Behavior` that describes the path to reproducing
51+the issue. While a `Trace` describes a syntactic path down the schema tree, a
52+`Behavior` describes which part of an interaction needs to be focused on for the
53+issue to manifest itself.
54+
55+In most places where we keep multiple issues (e.g. the output), to store them we
56+use a prefix tree map, indexed by both types and values of `Behavior`s. See
57+`Data.OpenApi.Compare.PathsPrefixTree`.
58+
59+Report
60+------
61+The checker ultimately outputs a list of `Issue`s, together with `Behavior`s
62+that cause them. We run the checker twice: forwards and backwards -- to detect
63+non-breaking changes. All of this is then compiled into a report in
64+`Data.OpenApi.Compare.Report`. The tree structure of headings is computed from
65+`Behavior`s, using `Jet`s to collapse particular sequences of behavior steps
66+into a single human-readable header item. The text of each paragraph comes from
67+`describeIssue`.
68+
69+We then use the `pandoc` library to render the report in a variety of formats.
70+
71+Formulas
72+--------
73+The schema has references, so it can end up being recursive. Similarly the
74+process for establishing compatibility can end up being recursive. To keep track
75+of this, the checker actually manipulates *formulas* which can have variables in
76+them, defined in (`Data.OpenApi.Compare.Formula`).
77+
78+A formula represents a set of issues, with the empty set corresponding to a
79+successful compatibility check. A formula can have conjunctions, where we only
80+report success if all parts succeeded, and if not we take the union of all
81+issues. A formula can also have disjunctions, but there it's impossible to
82+guarantee that the issues for the parts also make sense as issues for the whole
83+thing. Due to this if all disjuncted parts have issues we instead return a
84+different issue (usually non-descriptive).
85+
86+The `FormulaF` datatype implements a functor that carries formulaic calculations
87+as well as a result value (a co-Yoneda extension of formulas for the most part).
88+The Applicative interface provides conjunction, and the Alternative interface
89+provides disjunction.
90+
91+When we detect recursion we introduce a variable, compute the compatibility
92+check as a formula with that variable, and then we solve a fixed point equation
93+(`maxFixpoint`) with the variable to obtain the answer.
94+
95+This `FomulaF` is further wrapped in a couple monad transformers
96+(`SemanticCompatFormula`, `StructuralCompatFormula`), and the whole checker is
97+then implemented in the resulting Applicative using ApplicativeDo.
98+
99+Memoization
100+-----------
101+The `Data.OpenApi.Compare.Memo` module contains utilities for stateful
102+memoization and recursion detection. The entire checking process is memoized,
103+and can detect, propagate, and resolve recursion.
104+
105+Structural/Semantic Compatibility
106+---------------------------------
107+When we encounter two nodes we first try to optimistically establish whether
108+they're structurally equal, i.e. the trees are exactly the same modulo reference
109+inlining. This check is also memoized and recursion-aware. If the check succeeds
110+we conclude that the schemas are compatible. Otherwise we fall back to the
111+"normal" semantic compatibility.
112+
113+
114+JSON Schema
115+-----------
116+The language that describes JSON payloads is probably the richest part of
117+OpenAPI schemas, and the checker for it also comes with a lot of moving parts.
118+
119+JSON schema allows arbitrary set arithmetic using `allOf`, `anyOf`, `not` and
120+`oneOf`. To fully check `not` and `oneOf` we would need to compare set
121+subtraction, which involves negating a comparison result. If recursion is
122+involved we would need to be able to solve equations with negation, which we
123+cannot do. So `not` is unsupported altogether, and `oneOf` is only supported
124+when it has disjoint branches and behaves like `anyOf`.
125+
126+With that in mind a JSON schema is first pre-processed into a Disjunctive Normal
127+Form (`DNF` in `Data.OpenApi.Compare.Validate.Schema.DNF`). This pre-processing
128+happens in `Data.OpenApi.Compare.Validate.Schema.Process`.
129+
130+The DNF contains elementary clauses that match on a specific aspect of a JSON
131+object (`Condition`). Most clauses only affect objects of a single type, so JSON
132+values and clauses are segregated by type (`JsonType`, `TypedValue` in
133+`Data.OpenApi.Compare.Validate.Schema.JsonFormula`)
134+
135+Then we reduce the problem of comparing two DNF's to the comparison of a
136+conjunction of clauses in the producer with a single clause in the consumer
137+(`checkFormulas` in `Data.OpenApi.Compare.Validate.Schema`).
138+
139+When we do disjunction in the formula we lose information, so we try to avoid
140+having disjunctions in the consumer DNF. This is done by the means of
141+partitioning. We look for factors that would let us partition the set of all
142+objects into several bins, in a way that hopefully corresponds only a single
143+consumer to each producer. This is implemented in
144+`Data.OpenApi.Compare.Validate.Schema.Partition`. Currently we can only
145+partition by an `enum` value that is accessible via a chain of `required`
146+fields.
147+
148+The same partitioning mechanism is used to test whether the branches of a
149+`oneOf` are disjoint, and emit a warning otherwise.
docs/Integration_guide.mdnew file+51
@@ -0,0 +1,51 @@
1+# Integration guide
2+
3+## Github Actions
4+
5+In cases where your OpenAPI spec can be generated from your code, it can be useful to see how a pull request changes your API. For this very purpose we have a Github Action.
6+
7+### Generating the inputs
8+
9+To use it you need to first get the old (before the pull request) and new (after the pull request) specs.
10+
11+For example:
12+
13+- To get the old spec you can:
14+ - Download from a public "production" server (if your api is public and you provide an OpenAPI spec)
15+ - Checkout the base commit of the pull request and generate is programmatically.
16+
17+ You can checkout the base commit like so in Github Actions:
18+
19+ ```bash
20+ git checkout $(git merge-base ${{ github.event.pull_request.head.sha }} ${{ github.event.pull_request.base.sha }})
21+ ```
22+- To get the new spec you can:
23+ - Build and run the spec generation for the head commit of the pull request
24+
25+### Run the action
26+
27+In our example we have already generated the two OpenAPI specs and placed them in the `old-openapi.yaml` and `new-openapi.yaml` files.
28+
29+We can now run our action:
30+
31+##### NOTE: will only work after public release
32+
33+```yaml
34+- uses: typeable/comparest
35+ with:
36+ old: old-openapi.yaml
37+ new: new-openapi.yaml
38+ project_name: TEST
39+```
40+
41+This will create a check on the pull request displaying the changes (if there are any) similar to this:
42+
43+![](img/github-action-report.png)
44+
45+The check will show success when there are no breaking changes, and be neutral otherwise.
46+
47+## Integrating into something other than Github Actions
48+
49+We have a dedicated implementation only for Github Actions. However, we provide a generic CLI which can be used to integrate compaREST into arbitrary workflows and CI systems.
50+
51+You see an example of CLI usage in our [readme](../README.md#assessing-compatibility-automatically).
docs/User_guide.mdnew file+51
@@ -0,0 +1,51 @@
1+CompaREST User Guide
2+====================
3+
4+Running the Tool
5+----------------
6+The tool accepts two OpenAPI 3.0.0 schema files in either JSON or YAML format.
7+One is assumed to be the client version of the schema, and the other is the
8+server version.
9+
10+The tool will look for changes between the schemas and detect whether they are
11+breaking or not -- that is, if they prevent interoperability between the client
12+and the server.
13+
14+Running:
15+```
16+comparest -c client.json -s server.json
17+```
18+will output a markdown report of the changes to the standard output.
19+
20+Compatibility status can be signaled via the exit code using
21+`--signal-exit-code`. In case no report is needed, output can be suppressed with
22+`--silent`. For example:
23+```
24+comparest -c client.json -s server.json --signal-exit-code --silent
25+echo $?
26+```
27+If there were changes that the tool determined to be breaking, the exit code
28+will be 1. If there were some changes the tool couldn't understand, the exit
29+code will be 2. Otherwise if there were no breaking changes, the exit code will
30+be 0.
31+
32+Controlling the Report
33+----------------------
34+By default the report includes breaking changes, as well as non-breaking:
35+changes that would be considered breaking in the opposite direction. To only
36+include breaking changes in the report use `--only-breaking`. The `--all` option
37+restores the default behavior.
38+
39+The report can be formatted in a variety of ways supported by Pandoc. The `-o`
40+option causes the report to be written to a file. The format of the file is
41+determined from the extension. The supported extensions include:
42+ - `.md` for markdown
43+ - `.html` for an HTML snippet
44+ - `.rst` for restructured text
45+ - no extension for a self-contained HTML document with styles
46+
47+By default the report is split up into parts relating to different paths,
48+requests, responses, etc, using headers of various levels. Alternatively, the
49+report can use indended block-quotes to visualize the tree structure of the
50+report. The header style is enabled with `--header-style`, and the block-quote
51+style is enabled with `--folding-block-quotes-style`.
docs/img/github-action-report.pngnew file

Binary file not shown.

github-action/CompaREST/GitHub/API.hsnew file+86
@@ -0,0 +1,86 @@
1+module CompaREST.GitHub.API
2+ ( postStatus,
3+ postStatusProcessing,
4+ )
5+where
6+
7+import CompaREST.GitHub.Action.Config
8+import Control.Monad.Freer
9+import Control.Monad.Freer.GitHub
10+import Control.Monad.Freer.Reader
11+import Control.Monad.IO.Class
12+import Data.Aeson
13+import qualified Data.ByteString.Lazy.Char8 as BSLC
14+import Data.OpenApi.Compare.Report
15+import Data.Proxy
16+import Data.Text (Text)
17+import GitHub
18+import GitHub.Data.Checks
19+import GitHub.Endpoints.Checks
20+
21+postStatusProcessing ::
22+ (Members '[GitHub, Reader Config] effs, MonadIO (Eff effs)) =>
23+ Eff effs ()
24+postStatusProcessing = do
25+ Config {..} <- ask
26+ printJSON $
27+ sendGitHub $
28+ checkR
29+ repoOwner
30+ repoName
31+ Check
32+ { checkName = mkName Proxy $ "compaREST – " <> projectName
33+ , checkSha = sha
34+ , checkDetailsURL = Nothing
35+ , checkExternalId = Nothing
36+ , checkStatus = Just CheckInProgress
37+ , checkStartedAt = Nothing
38+ , checkConclusion = Nothing
39+ , checkCompletedAt = Nothing
40+ , checkOutput = Nothing
41+ , checkActions = Nothing
42+ }
43+
44+postStatus ::
45+ (Members '[GitHub, Reader Config] effs, MonadIO (Eff effs)) =>
46+ -- | 'Nothing' means that there were no changes at all
47+ Maybe (Text, ReportStatus) ->
48+ Eff effs ()
49+postStatus x = do
50+ let (body, (title, conclusion)) = case x of
51+ Just (b, s) -> (b,) $ case s of
52+ BreakingChanges -> ("⚠️ Breaking changes found!", CheckNeutral)
53+ NoBreakingChanges -> ("No breaking changes found ✨", CheckSuccess)
54+ OnlyUnsupportedChanges -> ("🤷 Couldn't determine compatibility", CheckNeutral)
55+ Nothing -> ("", ("✅ The API did not change", CheckSuccess))
56+ Config {..} <- ask
57+ printJSON $
58+ sendGitHub $
59+ checkR
60+ repoOwner
61+ repoName
62+ Check
63+ { checkName = mkName Proxy $ "compaREST – " <> projectName
64+ , checkSha = sha
65+ , checkDetailsURL = Nothing
66+ , checkExternalId = Nothing
67+ , checkStatus = Just CheckCompleted
68+ , checkStartedAt = Nothing
69+ , checkConclusion = Just conclusion
70+ , checkCompletedAt = Nothing
71+ , checkOutput =
72+ Just $
73+ CheckOutput
74+ { checkTitle = title
75+ , checkSummary = body
76+ , checkText = Nothing
77+ , checkAnnotations = Nothing
78+ , checkImages = Nothing
79+ }
80+ , checkActions = Nothing
81+ }
82+
83+printJSON :: MonadIO (Eff effs) => Eff effs Value -> Eff effs ()
84+printJSON m = do
85+ x <- m
86+ liftIO . BSLC.putStrLn $ encode x
github-action/CompaREST/GitHub/Action/Config.hsnew file+43
@@ -0,0 +1,43 @@
1+module CompaREST.GitHub.Action.Config
2+ ( Config (..),
3+ )
4+where
5+
6+import Data.Proxy
7+import Data.Text (Text)
8+import qualified Data.Text as T
9+import qualified GitHub as GH
10+
11+import System.Envy
12+
13+data Config = Config
14+ { githubToken :: GH.Auth
15+ , repoOwner :: GH.Name GH.Owner
16+ , repoName :: GH.Name GH.Repo
17+ , projectName :: Text
18+ , footerText :: Text
19+ , root :: FilePath
20+ , sha :: GH.Name GH.Commit
21+ }
22+
23+instance FromEnv Config where
24+ fromEnv _ = do
25+ token <- GH.OAuth <$> env "GITHUB_TOKEN"
26+ (owner, repo) <-
27+ T.split (== '/') <$> env "REPO" >>= \case
28+ [owner, name] -> pure (owner, name)
29+ _ -> fail "malformed repo"
30+ projectName <- env "PROJECT_NAME"
31+ footerText <- env "FOOTER"
32+ root <- envMaybe "ROOT" .!= "."
33+ sha <- env "SHA"
34+ pure $
35+ Config
36+ { githubToken = token
37+ , repoOwner = GH.mkName Proxy owner
38+ , repoName = GH.mkName Proxy repo
39+ , projectName = projectName
40+ , footerText = footerText
41+ , root = root
42+ , sha = GH.mkName Proxy sha
43+ }
github-action/Control/Monad/Freer/GitHub.hsnew file+27
@@ -0,0 +1,27 @@
1+module Control.Monad.Freer.GitHub
2+ ( GitHub (..),
3+ runGitHub,
4+ sendGitHub,
5+ )
6+where
7+
8+import Control.Monad.Freer
9+import Control.Monad.Freer.Error
10+import Control.Monad.IO.Class
11+import Data.Aeson
12+import GitHub hiding (Error)
13+import qualified GitHub as GH
14+
15+data GitHub r where
16+ SendGHRequest :: FromJSON x => Request 'RW x -> GitHub x
17+
18+runGitHub :: (Member (Error GH.Error) effs, MonadIO (Eff effs)) => Auth -> Eff (GitHub ': effs) ~> Eff effs
19+runGitHub auth =
20+ interpret
21+ ( \(SendGHRequest req) ->
22+ liftIO (executeRequest auth req)
23+ >>= either throwError pure
24+ )
25+
26+sendGitHub :: (FromJSON x, Member GitHub effs) => Request 'RW x -> Eff effs x
27+sendGitHub req = send $ SendGHRequest req
github-action/GitHub/Data/Checks.hsnew file+173
@@ -0,0 +1,173 @@
1+module GitHub.Data.Checks
2+ ( Check (..),
3+ CheckStatus (..),
4+ CheckConclusion (..),
5+ CheckOutput (..),
6+ CheckAnnotation (..),
7+ CheckAnnotationLevel (..),
8+ CheckImage (..),
9+ CheckAction (..),
10+ )
11+where
12+
13+import Data.Aeson
14+import Data.Aeson.Types
15+import Data.Text (Text)
16+import Data.Vector (Vector)
17+import GHC.Generics (Generic)
18+import GitHub
19+import GitHub.Internal.Prelude (UTCTime)
20+
21+data Check = Check
22+ { checkName :: !(Name Check)
23+ , checkSha :: !(Name Commit)
24+ , checkDetailsURL :: !(Maybe URL)
25+ , checkExternalId :: !(Maybe (Id Check))
26+ , checkStatus :: !(Maybe CheckStatus)
27+ , checkStartedAt :: !(Maybe UTCTime)
28+ , checkConclusion :: !(Maybe CheckConclusion)
29+ , checkCompletedAt :: !(Maybe UTCTime)
30+ , checkOutput :: !(Maybe CheckOutput)
31+ , checkActions :: !(Maybe (Vector CheckAction))
32+ }
33+ deriving stock (Show, Eq, Ord, Generic)
34+
35+instance ToJSON Check where
36+ toJSON (Check n sha durl eid s sa c ca o a) =
37+ object'
38+ [ "name" .= n
39+ , "head_sha" .= sha
40+ , "details_url" .= durl
41+ , "external_id" .= eid
42+ , "status" .= s
43+ , "started_at" .= sa
44+ , "conclusion" .= c
45+ , "completed_at" .= ca
46+ , "output" .= o
47+ , "actions" .= a
48+ ]
49+
50+data CheckStatus
51+ = CheckQueued
52+ | CheckInProgress
53+ | CheckCompleted
54+ deriving stock (Show, Enum, Bounded, Eq, Ord, Generic)
55+
56+instance ToJSON CheckStatus where
57+ toJSON CheckQueued = String "queued"
58+ toJSON CheckInProgress = String "in_progress"
59+ toJSON CheckCompleted = String "completed"
60+
61+data CheckConclusion
62+ = CheckActionRequired
63+ | CheckCancelled
64+ | CheckFailure
65+ | CheckNeutral
66+ | CheckSuccess
67+ | CheckSkipped
68+ | CheckStale
69+ | CheckTimedOut
70+ deriving stock (Show, Enum, Bounded, Eq, Ord, Generic)
71+
72+instance ToJSON CheckConclusion where
73+ toJSON CheckActionRequired = String "action_required"
74+ toJSON CheckCancelled = String "cancelled"
75+ toJSON CheckFailure = String "failure"
76+ toJSON CheckNeutral = String "neutral"
77+ toJSON CheckSuccess = String "success"
78+ toJSON CheckSkipped = String "skipped"
79+ toJSON CheckStale = String "stale"
80+ toJSON CheckTimedOut = String "timed_out"
81+
82+data CheckOutput = CheckOutput
83+ { checkTitle :: !Text
84+ , checkSummary :: !Text
85+ , checkText :: !(Maybe Text)
86+ , checkAnnotations :: !(Maybe (Vector CheckAnnotation))
87+ , checkImages :: !(Maybe (Vector CheckImage))
88+ }
89+ deriving stock (Show, Eq, Ord, Generic)
90+
91+instance ToJSON CheckOutput where
92+ toJSON (CheckOutput t s txt a i) =
93+ object'
94+ [ "title" .= t
95+ , "summary" .= s
96+ , "text" .= txt
97+ , "annotations" .= a
98+ , "images" .= i
99+ ]
100+
101+data CheckAnnotation = CheckAnnotation
102+ { checkPath :: !Text
103+ , checkStartLine :: !Int
104+ , checkEndLine :: !Int
105+ , checkStartColumn :: !(Maybe Int)
106+ , checkEndColumn :: !(Maybe Int)
107+ , checkAnnotationLevel :: !CheckAnnotationLevel
108+ , checkMessage :: !Text
109+ , checkTitle :: !(Maybe Text)
110+ , checkRawDetails :: !(Maybe Text)
111+ }
112+ deriving stock (Show, Eq, Ord, Generic)
113+
114+instance ToJSON CheckAnnotation where
115+ toJSON (CheckAnnotation p sl el sc ec al m t rd) =
116+ object'
117+ [ "path" .= p
118+ , "start_line" .= sl
119+ , "end_line" .= el
120+ , "start_column" .= sc
121+ , "end_column" .= ec
122+ , "annotation_level" .= al
123+ , "message" .= m
124+ , "title" .= t
125+ , "raw_details" .= rd
126+ ]
127+
128+data CheckAnnotationLevel
129+ = NoticeAnnotation
130+ | WarningAnnotation
131+ | FailureAnnotation
132+ deriving stock (Show, Enum, Bounded, Eq, Ord, Generic)
133+
134+instance ToJSON CheckAnnotationLevel where
135+ toJSON NoticeAnnotation = String "notice"
136+ toJSON WarningAnnotation = String "warning"
137+ toJSON FailureAnnotation = String "failure"
138+
139+data CheckImage = CheckImage
140+ { checkImageAlt :: !Text
141+ , checkImageURL :: !URL
142+ , checkImageCaption :: !(Maybe Text)
143+ }
144+ deriving stock (Show, Eq, Ord, Generic)
145+
146+instance ToJSON CheckImage where
147+ toJSON (CheckImage a url c) =
148+ object'
149+ [ "alt" .= a
150+ , "image_url" .= url
151+ , "caption" .= c
152+ ]
153+
154+data CheckAction = CheckAction
155+ { checkActionLabel :: !Text
156+ , checkActionDescription :: !Text
157+ , checkActionIdentifier :: !Text
158+ }
159+ deriving stock (Show, Eq, Ord, Generic)
160+
161+instance ToJSON CheckAction where
162+ toJSON (CheckAction l d i) =
163+ object'
164+ [ "label" .= l
165+ , "description" .= d
166+ , "identifier" .= i
167+ ]
168+
169+object' :: [Pair] -> Value
170+object' = object . filter notNull
171+ where
172+ notNull (_, Null) = False
173+ notNull (_, _) = True
github-action/GitHub/Endpoints/Checks.hsnew file+12
@@ -0,0 +1,12 @@
1+module GitHub.Endpoints.Checks
2+ ( checkR,
3+ )
4+where
5+
6+import Data.Aeson
7+import GitHub
8+import GitHub.Data.Checks
9+
10+checkR :: Name Owner -> Name Repo -> Check -> Request 'RW Value
11+checkR user repo =
12+ command Post ["repos", toPathPart user, toPathPart repo, "check-runs"] . encode
github-action/Main.hsnew file+66
@@ -0,0 +1,66 @@
1+module Main (main) where
2+
3+import CompaREST.GitHub.API
4+import CompaREST.GitHub.Action.Config
5+import Control.Exception
6+import Control.Monad.Freer
7+import Control.Monad.Freer.Error
8+import Control.Monad.Freer.GitHub
9+import Control.Monad.Freer.Reader
10+import Data.OpenApi.Compare.Run
11+import Data.Text (Text)
12+import qualified Data.Yaml.Aeson as Yaml
13+import qualified GitHub as GH
14+import System.Environment
15+import System.Envy (decodeEnv)
16+import System.FilePath ((</>))
17+import Text.Pandoc (runPure)
18+import Text.Pandoc.Builder
19+import Text.Pandoc.Options
20+import Text.Pandoc.Writers
21+
22+main :: IO ()
23+main = do
24+ cfg <- decodeEnv >>= either error pure
25+ getArgs >>= \case
26+ ["pre"] -> runPre cfg
27+ ["run"] -> do
28+ oldFile <- getEnv "OLD"
29+ newFile <- getEnv "NEW"
30+ runRun cfg (root cfg </> oldFile) (root cfg </> newFile)
31+ _ -> error "Invalid arguments."
32+
33+runner :: Config -> Eff '[GitHub, Error GH.Error, Reader Config, IO] a -> IO a
34+runner cfg =
35+ runM @IO . runReader cfg
36+ . flip (handleError @GH.Error) (error . displayException)
37+ . runGitHub (githubToken cfg)
38+
39+runPre :: Config -> IO ()
40+runPre cfg = runner cfg postStatusProcessing
41+
42+runRun :: Config -> FilePath -> FilePath -> IO ()
43+runRun cfg old' new' = runner cfg $ do
44+ old <- Yaml.decodeFileThrow old'
45+ new <- Yaml.decodeFileThrow new'
46+ let reportConfig =
47+ ReportConfig
48+ { treeStyle = FoldingBlockquotesTreeStyle
49+ , reportMode = All
50+ }
51+ (report, status) = runReport reportConfig (old, new)
52+
53+ body = markdown report <> "\n\n" <> footerText cfg
54+
55+ result =
56+ if old == new
57+ then Nothing
58+ else Just (body, status)
59+ postStatus result
60+
61+markdown :: Blocks -> Text
62+markdown =
63+ either (error . displayException) id
64+ . runPure
65+ . writeHtml5String def
66+ . doc
nix/ci.nixnew file+6
@@ -0,0 +1,6 @@
1+{ sources ? import ./sources.nix
2+, pkgs ? import sources.nixpkgs { }
3+}:
4+pkgs.mkShell {
5+ packages = [ pkgs.haskellPackages.fourmolu ];
6+}
nix/sources.jsonnew file+50
@@ -0,0 +1,50 @@
1+{
2+ "haskellNix": {
3+ "branch": "master",
4+ "description": "Alternative Haskell Infrastructure for Nixpkgs",
5+ "homepage": "https://input-output-hk.github.io/haskell.nix",
6+ "owner": "input-output-hk",
7+ "repo": "haskell.nix",
8+ "rev": "c8a8f57de3c14fcf1c5cda045ed885fb6d06a00e",
9+ "sha256": "0frzmvz8rhb4vgx20qms9xxqbkmg5pwivis4qis63ny5wxclzb34",
10+ "type": "tarball",
11+ "url": "https://github.com/input-output-hk/haskell.nix/archive/c8a8f57de3c14fcf1c5cda045ed885fb6d06a00e.tar.gz",
12+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
13+ },
14+ "niv": {
15+ "branch": "master",
16+ "description": "Easy dependency management for Nix projects",
17+ "homepage": "https://github.com/nmattia/niv",
18+ "owner": "nmattia",
19+ "repo": "niv",
20+ "rev": "5830a4dd348d77e39a0f3c4c762ff2663b602d4c",
21+ "sha256": "1d3lsrqvci4qz2hwjrcnd8h5vfkg8aypq3sjd4g3izbc8frwz5sm",
22+ "type": "tarball",
23+ "url": "https://github.com/nmattia/niv/archive/5830a4dd348d77e39a0f3c4c762ff2663b602d4c.tar.gz",
24+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
25+ },
26+ "nix-filter": {
27+ "branch": "master",
28+ "description": "a small self-container source filtering lib",
29+ "homepage": "",
30+ "owner": "numtide",
31+ "repo": "nix-filter",
32+ "rev": "3c9e33ed627e009428197b07216613206f06ed80",
33+ "sha256": "19w142crrkywxynmyw4rhz4nglrg64yjawfkw3j91qwkwbfjds84",
34+ "type": "tarball",
35+ "url": "https://github.com/numtide/nix-filter/archive/3c9e33ed627e009428197b07216613206f06ed80.tar.gz",
36+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
37+ },
38+ "nixpkgs": {
39+ "branch": "master",
40+ "description": "Nix Packages collection",
41+ "homepage": "",
42+ "owner": "NixOS",
43+ "repo": "nixpkgs",
44+ "rev": "f0fe5e9ba08e658474476317717aca7acacef4d4",
45+ "sha256": "1n5syynwmg3x8lj2kyb2ilbxw5nmw7f2kw6yxsi8qadx91hrvkwm",
46+ "type": "tarball",
47+ "url": "https://github.com/NixOS/nixpkgs/archive/f0fe5e9ba08e658474476317717aca7acacef4d4.tar.gz",
48+ "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
49+ }
50+}
nix/sources.nixnew file+174
@@ -0,0 +1,174 @@
1+# This file has been generated by Niv.
2+
3+let
4+
5+ #
6+ # The fetchers. fetch_<type> fetches specs of type <type>.
7+ #
8+
9+ fetch_file = pkgs: name: spec:
10+ let
11+ name' = sanitizeName name + "-src";
12+ in
13+ if spec.builtin or true then
14+ builtins_fetchurl { inherit (spec) url sha256; name = name'; }
15+ else
16+ pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
17+
18+ fetch_tarball = pkgs: name: spec:
19+ let
20+ name' = sanitizeName name + "-src";
21+ in
22+ if spec.builtin or true then
23+ builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
24+ else
25+ pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
26+
27+ fetch_git = name: spec:
28+ let
29+ ref =
30+ if spec ? ref then spec.ref else
31+ if spec ? branch then "refs/heads/${spec.branch}" else
32+ if spec ? tag then "refs/tags/${spec.tag}" else
33+ abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
34+ in
35+ builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; };
36+
37+ fetch_local = spec: spec.path;
38+
39+ fetch_builtin-tarball = name: throw
40+ ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
41+ $ niv modify ${name} -a type=tarball -a builtin=true'';
42+
43+ fetch_builtin-url = name: throw
44+ ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
45+ $ niv modify ${name} -a type=file -a builtin=true'';
46+
47+ #
48+ # Various helpers
49+ #
50+
51+ # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
52+ sanitizeName = name:
53+ (
54+ concatMapStrings (s: if builtins.isList s then "-" else s)
55+ (
56+ builtins.split "[^[:alnum:]+._?=-]+"
57+ ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
58+ )
59+ );
60+
61+ # The set of packages used when specs are fetched using non-builtins.
62+ mkPkgs = sources: system:
63+ let
64+ sourcesNixpkgs =
65+ import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
66+ hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
67+ hasThisAsNixpkgsPath = <nixpkgs> == ./.;
68+ in
69+ if builtins.hasAttr "nixpkgs" sources
70+ then sourcesNixpkgs
71+ else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
72+ import <nixpkgs> {}
73+ else
74+ abort
75+ ''
76+ Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
77+ add a package called "nixpkgs" to your sources.json.
78+ '';
79+
80+ # The actual fetching function.
81+ fetch = pkgs: name: spec:
82+
83+ if ! builtins.hasAttr "type" spec then
84+ abort "ERROR: niv spec ${name} does not have a 'type' attribute"
85+ else if spec.type == "file" then fetch_file pkgs name spec
86+ else if spec.type == "tarball" then fetch_tarball pkgs name spec
87+ else if spec.type == "git" then fetch_git name spec
88+ else if spec.type == "local" then fetch_local spec
89+ else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
90+ else if spec.type == "builtin-url" then fetch_builtin-url name
91+ else
92+ abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
93+
94+ # If the environment variable NIV_OVERRIDE_${name} is set, then use
95+ # the path directly as opposed to the fetched source.
96+ replace = name: drv:
97+ let
98+ saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
99+ ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
100+ in
101+ if ersatz == "" then drv else
102+ # this turns the string into an actual Nix path (for both absolute and
103+ # relative paths)
104+ if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
105+
106+ # Ports of functions for older nix versions
107+
108+ # a Nix version of mapAttrs if the built-in doesn't exist
109+ mapAttrs = builtins.mapAttrs or (
110+ f: set: with builtins;
111+ listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
112+ );
113+
114+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
115+ range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
116+
117+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
118+ stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
119+
120+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
121+ stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
122+ concatMapStrings = f: list: concatStrings (map f list);
123+ concatStrings = builtins.concatStringsSep "";
124+
125+ # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
126+ optionalAttrs = cond: as: if cond then as else {};
127+
128+ # fetchTarball version that is compatible between all the versions of Nix
129+ builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
130+ let
131+ inherit (builtins) lessThan nixVersion fetchTarball;
132+ in
133+ if lessThan nixVersion "1.12" then
134+ fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
135+ else
136+ fetchTarball attrs;
137+
138+ # fetchurl version that is compatible between all the versions of Nix
139+ builtins_fetchurl = { url, name ? null, sha256 }@attrs:
140+ let
141+ inherit (builtins) lessThan nixVersion fetchurl;
142+ in
143+ if lessThan nixVersion "1.12" then
144+ fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
145+ else
146+ fetchurl attrs;
147+
148+ # Create the final "sources" from the config
149+ mkSources = config:
150+ mapAttrs (
151+ name: spec:
152+ if builtins.hasAttr "outPath" spec
153+ then abort
154+ "The values in sources.json should not have an 'outPath' attribute"
155+ else
156+ spec // { outPath = replace name (fetch config.pkgs name spec); }
157+ ) config.sources;
158+
159+ # The "config" used by the fetchers
160+ mkConfig =
161+ { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
162+ , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
163+ , system ? builtins.currentSystem
164+ , pkgs ? mkPkgs sources system
165+ }: rec {
166+ # The sources, i.e. the attribute set of spec name to spec
167+ inherit sources;
168+
169+ # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
170+ inherit pkgs;
171+ };
172+
173+in
174+mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }
policeman.jsonnew file+6
@@ -0,0 +1,6 @@
1+[{
2+ "file": "compaREST.cabal",
3+ "annotation_level": "notice",
4+ "end_line": 0,
5+ "start_line": 0
6+}]
src/Data/HList.hs+11−11
@@ -1,11 +1,11 @@
11module Data.HList
2− ( Has
3− , HasAll
4− , getH
5− , HList (..)
6− , singletonH
7− , ReassembleHList
8− , reassemble
2+ ( Has,
3+ HasAll,
4+ getH,
5+ HList (..),
6+ singletonH,
7+ ReassembleHList,
8+ reassemble,
99 )
1010where
1111
@@ -40,8 +40,8 @@ instance (Has' x xs t, HeadEq x (y : xs) ~ 'False) => Has' x (y ': xs) 'False wh
4040 {-# INLINE getH #-}
4141
4242instance
43− TypeError ('ShowType x ':<>: 'Text " is not a part of the list.")
44− => Has' x '[] 'False
43+ TypeError ( 'ShowType x ':<>: 'Text " is not a part of the list.") =>
44+ Has' x '[] 'False
4545 where
4646 getH HNil = undefined
4747 {-# INLINE getH #-}
@@ -74,8 +74,8 @@ instance ReassembleHList' (x ': xs) '[] 'False where
7474 {-# INLINE reassemble #-}
7575
7676instance
77− (Has y xs, ReassembleHList' xs ys f, (xs == (y ': ys)) ~ 'False)
78− => ReassembleHList' xs (y ': ys) 'False
77+ (Has y xs, ReassembleHList' xs ys f, (xs == (y ': ys)) ~ 'False) =>
78+ ReassembleHList' xs (y ': ys) 'False
7979 where
8080 reassemble xs = getH @y xs `HCons` reassemble xs
8181 {-# INLINE reassemble #-}
src/OpenAPI/Checker/Behavior.hs → src/Data/OpenApi/Compare/Behavior.hsrenamed+13−13
@@ -1,22 +1,22 @@
1−module OpenAPI.Checker.Behavior
2− ( BehaviorLevel (..)
3− , Behavable (..)
4− , IssueKind (..)
5− , Issuable (..)
6− , Orientation (..)
7− , toggleOrientation
8− , Behavior
9− , AnIssue (..)
10− , withClass
11− , anIssueKind
12− , relatedAnIssues
1+module Data.OpenApi.Compare.Behavior
2+ ( BehaviorLevel (..),
3+ Behavable (..),
4+ IssueKind (..),
5+ Issuable (..),
6+ Orientation (..),
7+ toggleOrientation,
8+ Behavior,
9+ AnIssue (..),
10+ withClass,
11+ anIssueKind,
12+ relatedAnIssues,
1313 )
1414where
1515
1616import Data.Aeson
1717import Data.Kind
18+import Data.OpenApi.Compare.Paths
1819import Data.Typeable
19−import OpenAPI.Checker.Paths
2020import Text.Pandoc.Builder
2121
2222-- | Kind
src/OpenAPI/Checker/Common.hs → src/Data/OpenApi/Compare/Common.hsrenamed+2−2
@@ -1,5 +1,5 @@
1−module OpenAPI.Checker.Common
2− ( zipAll
1+module Data.OpenApi.Compare.Common
2+ ( zipAll,
33 )
44where
55
src/OpenAPI/Checker/Formula.hs → src/Data/OpenApi/Compare/Formula.hsrenamed+35−25
@@ -1,20 +1,21 @@
1−module OpenAPI.Checker.Formula
2− ( FormulaF
3− , VarRef
4− , variable
5− , eitherOf
6− , anError
7− , errors
8− , calculate
9− , maxFixpoint
10− , mapErrors
11− ) where
1+module Data.OpenApi.Compare.Formula
2+ ( FormulaF,
3+ VarRef,
4+ variable,
5+ eitherOf,
6+ anError,
7+ errors,
8+ calculate,
9+ maxFixpoint,
10+ mapErrors,
11+ )
12+where
1213
1314import Data.Kind
14−import Data.Monoid
1515import qualified Data.List.NonEmpty as NE
16−import OpenAPI.Checker.Paths
17−import qualified OpenAPI.Checker.PathsPrefixTree as P
16+import Data.Monoid
17+import Data.OpenApi.Compare.Paths
18+import qualified Data.OpenApi.Compare.PathsPrefixTree as P
1819
1920type VarRef = Int
2021
@@ -24,15 +25,24 @@ type VarRef = Int
2425-- fixpoints always exist, i.e. that @x = f x@ has at least one solution.
2526data FormulaF (q :: k -> k -> Type) (f :: k -> Type) (r :: k) (a :: Type) where
2627 Result :: a -> FormulaF q f r a
27− Errors :: !(P.PathsPrefixTree q f r) -> FormulaF q f r a
28− -- ^ invariant: never empty
29− Apply :: FormulaF q f r (b -> c) -> FormulaF q f r b -> (c -> a) -> FormulaF q f r a
30− -- ^ invariant: at least one of LHS and RHS is not 'Errors', and they are
28+ Errors ::
29+ !(P.PathsPrefixTree q f r) ->
30+ -- | invariant: never empty
31+ FormulaF q f r a
32+ Apply ::
33+ FormulaF q f r (b -> c) ->
34+ FormulaF q f r b ->
35+ (c -> a) ->
36+ -- | invariant: at least one of LHS and RHS is not 'Errors', and they are
3137 -- both not 'Result'
32− SelectFirst :: NE.NonEmpty (SomeFormulaF b)
33− -> !(P.PathsPrefixTree q f r) -> (b -> a) -> FormulaF q f r a
34− -- ^ invariant: the list doesn't contain any 'Result's, 'Errors' or
38+ FormulaF q f r a
39+ SelectFirst ::
40+ NE.NonEmpty (SomeFormulaF b) ->
41+ !(P.PathsPrefixTree q f r) ->
42+ (b -> a) ->
43+ -- | invariant: the list doesn't contain any 'Result's, 'Errors' or
3544 -- 'SelectFirst'
45+ FormulaF q f r a
3646 Variable :: !VarRef -> a -> FormulaF q f r a
3747
3848mkApply :: FormulaF q f r (b -> c) -> FormulaF q f r b -> (c -> a) -> FormulaF q f r a
@@ -44,13 +54,13 @@ mkApply f x h = Apply f x h
4454mkSelectFirst :: [SomeFormulaF b] -> P.PathsPrefixTree q f r -> (b -> a) -> FormulaF q f r a
4555mkSelectFirst fs allE h = case foldMap check fs of
4656 (First (Just x), _) -> Result (h x)
47− (First Nothing, x:xs) -> SelectFirst (x NE.:| xs) allE h
57+ (First Nothing, x : xs) -> SelectFirst (x NE.:| xs) allE h
4858 (First Nothing, []) -> Errors allE
4959 where
5060 check (SomeFormulaF (Result x)) = (First (Just x), mempty)
5161 check (SomeFormulaF (Errors _)) = (mempty, mempty)
52− check (SomeFormulaF (SelectFirst xs _ h'))
53− = (mempty, NE.toList (fmap (fmap h') xs))
62+ check (SomeFormulaF (SelectFirst xs _ h')) =
63+ (mempty, NE.toList (fmap (fmap h') xs))
5464 check x = (mempty, [x])
5565
5666data SomeFormulaF (a :: Type) where
@@ -96,7 +106,7 @@ calculate (Apply f x h) = case calculate f of
96106 Right x' -> Right (h (f' x'))
97107calculate (SelectFirst xs e h) = go (NE.toList xs)
98108 where
99− go (SomeFormulaF r:rs) = case calculate r of
109+ go (SomeFormulaF r : rs) = case calculate r of
100110 Left _ -> go rs
101111 Right x -> Right (h x)
102112 go [] = Left e
src/Data/OpenApi/Compare/Memo.hsnew file+164
@@ -0,0 +1,164 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+
3+-- | Utilities for effectfully memoizing other, more effectful functions.
4+module Data.OpenApi.Compare.Memo
5+ ( MonadMemo,
6+ MemoState,
7+ runMemo,
8+ modifyMemoNonce,
9+ KnotTier (..),
10+ unknot,
11+ memoWithKnot,
12+ memoTaggedWithKnot,
13+ )
14+where
15+
16+import Control.Monad.State
17+import Data.Dynamic
18+import qualified Data.Map as M
19+import Data.Tagged
20+import qualified Data.TypeRepMap as T
21+import Data.Void
22+import Type.Reflection
23+
24+data Progress a = Finished a | Started | TyingKnot Dynamic
25+
26+data MemoMap a where
27+ MemoMap :: !(M.Map k (Progress v)) -> MemoMap (k, v)
28+
29+data MemoState s = MemoState s (T.TypeRepMap MemoMap)
30+
31+-- | An effectful memoization monad.
32+type MonadMemo s m = MonadState (MemoState s) m
33+
34+memoStateLookup ::
35+ forall k v s.
36+ (Typeable k, Typeable v, Ord k) =>
37+ k ->
38+ MemoState s ->
39+ Maybe (Progress v)
40+memoStateLookup k (MemoState _ t) = case T.lookup @(k, v) t of
41+ Just (MemoMap m) -> M.lookup k m
42+ Nothing -> Nothing
43+
44+memoStateInsert ::
45+ forall k v s.
46+ (Typeable k, Typeable v, Ord k) =>
47+ k ->
48+ Progress v ->
49+ MemoState s ->
50+ MemoState s
51+memoStateInsert k x (MemoState s t) = MemoState s $ T.insert (MemoMap m'') t
52+ where
53+ m'' = M.insert k x m'
54+ m' = case T.lookup @(k, v) t of
55+ Just (MemoMap m) -> m
56+ Nothing -> M.empty
57+
58+modifyMemoNonce :: MonadMemo s m => (s -> s) -> m s
59+modifyMemoNonce f = do
60+ MemoState s t <- get
61+ put $ MemoState (f s) t
62+ pure s
63+
64+-- | Run a memoized computation.
65+runMemo :: Monad m => s -> StateT (MemoState s) m a -> m a
66+runMemo s = (`evalStateT` MemoState s T.empty)
67+
68+-- | A description of how to effectfully tie knots in type @v@, using the @m@
69+-- monad, and by sharing some @d@ data among the recursive instances.
70+data KnotTier v d m = KnotTier
71+ { -- | Create some data that will be connected to this knot
72+ onKnotFound :: m d
73+ , -- | This is what the knot will look like as a value
74+ -- to the inner computations
75+ onKnotUsed :: d -> m v
76+ , -- | Once we're done and we're outside, tie the
77+ -- knot using the datum
78+ tieKnot :: d -> v -> m v
79+ }
80+
81+unknot :: KnotTier v Void m
82+unknot =
83+ KnotTier
84+ { onKnotFound = error "Recursion detected"
85+ , onKnotUsed = absurd
86+ , tieKnot = absurd
87+ }
88+
89+-- | Run a potentially recursive computation. The provided key will be used to
90+-- refer to the result of this computation. If during the computation, another
91+-- attempt to run the computation with the same key is made, we run a
92+-- tying-the-knot procedure.
93+--
94+-- If another attempt to run the computation with the same key is made
95+-- *after we're done*, we will return the memoized value.
96+memoWithKnot ::
97+ forall k v d m s.
98+ (Typeable k, Typeable v, Typeable d, Ord k, MonadMemo s m) =>
99+ KnotTier v d m ->
100+ -- | the computation to memoize
101+ m v ->
102+ -- | key for memoization
103+ k ->
104+ m v
105+memoWithKnot tier f k =
106+ memoStateLookup @k @v k <$> get >>= \case
107+ Just (Finished v) -> pure v
108+ Just Started -> do
109+ d <- onKnotFound tier
110+ modify $ memoStateInsert @k @v k (TyingKnot $ toDyn d)
111+ onKnotUsed tier d
112+ Just (TyingKnot dyn) -> case fromDynamic dyn of
113+ Just d -> onKnotUsed tier d
114+ Nothing ->
115+ error $
116+ "Type mismatch when examining the knot of "
117+ <> show (typeRep @(k -> v))
118+ <> ": expected "
119+ <> show (typeRep @d)
120+ <> ", got "
121+ <> show (dynTypeRep dyn)
122+ Nothing -> do
123+ modify $ memoStateInsert @k @v k Started
124+ v <- f
125+ v' <-
126+ memoStateLookup @k @v k <$> get >>= \case
127+ Just Started -> pure v
128+ Just (TyingKnot dyn) -> case fromDynamic dyn of
129+ Just d -> tieKnot tier d v
130+ Nothing ->
131+ error $
132+ "Type mismatch when tying the knot of "
133+ <> show (typeRep @(k -> v))
134+ <> ": expected "
135+ <> show (typeRep @d)
136+ <> ", got "
137+ <> show (dynTypeRep dyn)
138+ Just (Finished _) ->
139+ error $
140+ "Unexpected Finished when memoizing "
141+ <> show (typeRep @(k -> v))
142+ Nothing -> pure v
143+ -- Normally this would be an error, but the underlying monad can refuse
144+ -- to remember memoization state
145+ modify $ memoStateInsert @k @v k (Finished v')
146+ pure v'
147+
148+-- | Disambiguate memoized computations with an arbitrary tag.
149+memoTaggedWithKnot ::
150+ forall t k v d m s.
151+ ( Typeable t
152+ , Typeable k
153+ , Typeable v
154+ , Typeable d
155+ , Ord k
156+ , MonadMemo s m
157+ ) =>
158+ KnotTier v d m ->
159+ m v ->
160+ k ->
161+ m v
162+memoTaggedWithKnot tier f k =
163+ withTypeable (typeRepKind $ typeRep @t) $
164+ memoWithKnot tier f (Tagged @t k)
src/OpenAPI/Checker/Orphans.hs → src/Data/OpenApi/Compare/Orphans.hsrenamed+1−1
@@ -1,6 +1,6 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Orphans () where
3+module Data.OpenApi.Compare.Orphans () where
44
55import Control.Comonad.Env
66import qualified Data.HashMap.Strict.InsOrd as IOHM
src/OpenAPI/Checker/Paths.hs → src/Data/OpenApi/Compare/Paths.hsrenamed+10−10
@@ -1,18 +1,18 @@
11-- | Utilities for traversing heterogeneous trees. A heterogeneous tree is a
22-- collection of datatypes that "contain" eachother in some form of tree
33-- structure.
4−module OpenAPI.Checker.Paths
5− ( NiceQuiver
6− , AdditionalQuiverConstraints
7− , Paths (..)
8− , DiffPaths (..)
9− , catDiffPaths
10− , AnItem (..)
11− , step
4+module Data.OpenApi.Compare.Paths
5+ ( NiceQuiver,
6+ AdditionalQuiverConstraints,
7+ Paths (..),
8+ DiffPaths (..),
9+ catDiffPaths,
10+ AnItem (..),
11+ step,
1212
1313 -- * Reexports
14− , (>>>)
15− , (<<<)
14+ (>>>),
15+ (<<<),
1616 )
1717where
1818
src/OpenAPI/Checker/PathsPrefixTree.hs → src/Data/OpenApi/Compare/PathsPrefixTree.hsrenamed+39−39
@@ -1,22 +1,22 @@
11{-# LANGUAGE QuantifiedConstraints #-}
22
3−module OpenAPI.Checker.PathsPrefixTree
4− ( PathsPrefixTree (PathsPrefixNode)
5− , AStep (..)
6− , empty
7− , singleton
8− , fromList
9− , null
10− , foldWith
11− , toList
12− , filter
13− , filterWithKey
14− , takeSubtree
15− , lookup
16− , embed
17− , size
18− , partition
19− , map
3+module Data.OpenApi.Compare.PathsPrefixTree
4+ ( PathsPrefixTree (PathsPrefixNode),
5+ AStep (..),
6+ empty,
7+ singleton,
8+ fromList,
9+ null,
10+ foldWith,
11+ toList,
12+ filter,
13+ filterWithKey,
14+ takeSubtree,
15+ lookup,
16+ embed,
17+ size,
18+ partition,
19+ map,
2020 )
2121where
2222
@@ -27,15 +27,15 @@ import qualified Data.HashMap.Strict as HM
2727import Data.Kind
2828import qualified Data.Map as M
2929import Data.Monoid
30+import Data.OpenApi.Compare.Paths
3031import qualified Data.Set as S
3132import qualified Data.Text as T
3233import Data.Type.Equality
3334import qualified Data.TypeRepMap as TRM
3435import qualified Data.Vector as V
3536import qualified GHC.Exts as Exts
36−import OpenAPI.Checker.Paths
3737import Type.Reflection
38−import Prelude hiding (filter, map, null, lookup)
38+import Prelude hiding (filter, lookup, map, null)
3939
4040-- | A list of @AnItem r f@, but optimized into a prefix tree.
4141data PathsPrefixTree (q :: k -> k -> Type) (f :: k -> Type) (r :: k) = PathsPrefixTree
@@ -102,11 +102,11 @@ deriving stock instance Eq (PathsPrefixTree q f a)
102102
103103-- Kind of orphan. Treat the map as an infinite tuple of @Maybe (f a)@'s, where
104104-- the components are ordered by the @SomeTypeRep@ of the @a@.
105−compareTRM
106− :: (forall a. Typeable a => Ord (f a))
107− => TRM.TypeRepMap f
108− -> TRM.TypeRepMap f
109− -> Ordering
105+compareTRM ::
106+ (forall a. Typeable a => Ord (f a)) =>
107+ TRM.TypeRepMap f ->
108+ TRM.TypeRepMap f ->
109+ Ordering
110110compareTRM s1 s2 =
111111 foldMap (\k -> compareMaybe compareW (M.lookup k m1) (M.lookup k m2)) mKeys
112112 where
@@ -116,11 +116,11 @@ compareTRM s1 s2 =
116116 compareMaybe _ Nothing (Just _) = LT
117117 compareMaybe _ (Just _) Nothing = GT
118118 compareMaybe cmp (Just x) (Just y) = cmp x y
119− compareW
120− :: (forall a. Typeable a => Ord (f a))
121− => TRM.WrapTypeable f
122− -> TRM.WrapTypeable f
123− -> Ordering
119+ compareW ::
120+ (forall a. Typeable a => Ord (f a)) =>
121+ TRM.WrapTypeable f ->
122+ TRM.WrapTypeable f ->
123+ Ordering
124124 compareW (TRM.WrapTypeable (x :: f a)) (TRM.WrapTypeable (y :: f b))
125125 | Just Refl <- testEquality (typeRep @a) (typeRep @b) = compare x y
126126 | otherwise = EQ -- unreachable
@@ -172,10 +172,10 @@ instance Monoid (ASet a) where
172172 mempty = AnEmptySet
173173
174174data AStep (q :: k -> k -> Type) (f :: k -> Type) (r :: k) (a :: k) where
175− AStep
176− :: NiceQuiver q r a =>
177− !(M.Map (q r a) (PathsPrefixTree q f a))
178− -> AStep q f r a
175+ AStep ::
176+ NiceQuiver q r a =>
177+ !(M.Map (q r a) (PathsPrefixTree q f a)) ->
178+ AStep q f r a
179179
180180mapAStep :: (forall x. f x -> f x) -> AStep q f r a -> AStep q f r a
181181mapAStep f (AStep m) = AStep $ M.map (map f) m
@@ -215,12 +215,12 @@ null :: PathsPrefixTree q f r -> Bool
215215null (PathsPrefixTree AnEmptySet s) = all (\(TRM.WrapTypeable (AStep x)) -> all null x) (Exts.toList s)
216216null _ = False
217217
218−foldWith
219− :: forall q f m r.
220− Monoid m
221− => (forall a. Ord (f a) => Paths q r a -> f a -> m)
222− -> PathsPrefixTree q f r
223− -> m
218+foldWith ::
219+ forall q f m r.
220+ Monoid m =>
221+ (forall a. Ord (f a) => Paths q r a -> f a -> m) ->
222+ PathsPrefixTree q f r ->
223+ m
224224foldWith k = goTPT Root
225225 where
226226 goTPT :: forall a. Paths q r a -> PathsPrefixTree q f a -> m
src/OpenAPI/Checker/References.hs → src/Data/OpenApi/Compare/References.hsrenamed+11−12
@@ -1,9 +1,9 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.References
4− ( Step (..)
5− , dereference
6− , Typeable
3+module Data.OpenApi.Compare.References
4+ ( Step (..),
5+ dereference,
6+ Typeable,
77 )
88where
99
@@ -11,19 +11,18 @@ import Data.HList
1111import qualified Data.HashMap.Strict.InsOrd as IOHM
1212import Data.Maybe
1313import Data.OpenApi
14−import Data.Typeable
15−import OpenAPI.Checker.Orphans ()
16−import OpenAPI.Checker.Subtree
14+import Data.OpenApi.Compare.Orphans ()
15+import Data.OpenApi.Compare.Subtree
1716
1817instance Typeable a => Steppable (Referenced a) a where
1918 data Step (Referenced a) a = InlineStep
2019 deriving stock (Eq, Ord, Show)
2120
22−dereference
23− :: Typeable a
24− => Traced (Definitions a)
25− -> Traced (Referenced a)
26− -> Traced a
21+dereference ::
22+ Typeable a =>
23+ Traced (Definitions a) ->
24+ Traced (Referenced a) ->
25+ Traced a
2726dereference defs x = case extract x of
2827 Inline a ->
2928 traced (ask x >>> step InlineStep) a
src/OpenAPI/Checker/Report.hs → src/Data/OpenApi/Compare/Report.hsrenamed+152−131
@@ -1,13 +1,13 @@
1−module OpenAPI.Checker.Report
2− ( generateReport
3− , CheckerOutput(..)
4− , ReportInput (..)
5− , segregateIssues
6− , ReportStatus (..)
7− , Pandoc
8− , ReportConfig (..)
9− , ReportTreeStyle (..)
10− , ReportMode (..)
1+module Data.OpenApi.Compare.Report
2+ ( generateReport,
3+ CheckerOutput (..),
4+ ReportInput (..),
5+ segregateIssues,
6+ ReportStatus (..),
7+ Pandoc,
8+ ReportConfig (..),
9+ ReportTreeStyle (..),
10+ ReportMode (..),
1111 )
1212where
1313
@@ -23,6 +23,15 @@ import qualified Data.Map as M
2323import Data.Map.Ordered (OMap)
2424import qualified Data.Map.Ordered as OM
2525import Data.Maybe
26+import Data.OpenApi.Compare.Behavior
27+import Data.OpenApi.Compare.Paths
28+import Data.OpenApi.Compare.PathsPrefixTree hiding (empty)
29+import qualified Data.OpenApi.Compare.PathsPrefixTree as P hiding (empty)
30+import Data.OpenApi.Compare.Report.Jet
31+import Data.OpenApi.Compare.Subtree (invertIssueOrientationP)
32+import Data.OpenApi.Compare.Validate.OpenApi
33+import Data.OpenApi.Compare.Validate.Schema.Issues
34+import Data.OpenApi.Compare.Validate.Schema.TypedJson
2635import Data.OpenUnion
2736import Data.OpenUnion.Extra
2837import Data.Set
@@ -32,15 +41,6 @@ import qualified Data.Text as T
3241import Data.TypeRepMap hiding (empty)
3342import Data.Typeable
3443import Generic.Data
35−import OpenAPI.Checker.Behavior
36−import OpenAPI.Checker.Paths
37−import OpenAPI.Checker.PathsPrefixTree hiding (empty)
38−import qualified OpenAPI.Checker.PathsPrefixTree as P hiding (empty)
39−import OpenAPI.Checker.Report.Jet
40−import OpenAPI.Checker.Subtree (invertIssueOrientationP)
41−import OpenAPI.Checker.Validate.OpenApi
42−import OpenAPI.Checker.Validate.Schema.TypedJson
43−import OpenAPI.Checker.Validate.Schema.Issues
4444import Text.Pandoc.Builder
4545
4646type Changes = P.PathsPrefixTree Behave AnIssue 'APILevel
@@ -54,22 +54,27 @@ data CheckerOutput = CheckerOutput
5454 deriving anyclass (ToJSON)
5555
5656data ReportInput = ReportInput
57− { breakingChanges :: Changes -- ^ forward 'CertainIssue', 'ProbablyIssue' and 'Comment'
58− , nonBreakingChanges :: Changes -- ^ backward 'CertainIssue', 'ProbablyIssue' and 'Comment', except those shadowed by 'relatedIssues'
59− , unsupportedChanges :: Changes -- ^ forward and backward 'Unsupported' (assumed to be the same anyway)
60− , schemaIssues :: Changes -- ^ forward and backward 'SchemaInvalid' (assumed to be the same anyway)
57+ { -- | forward 'CertainIssue', 'ProbablyIssue' and 'Comment'
58+ breakingChanges :: Changes
59+ , -- | backward 'CertainIssue', 'ProbablyIssue' and 'Comment', except those shadowed by 'relatedIssues'
60+ nonBreakingChanges :: Changes
61+ , -- | forward and backward 'Unsupported' (assumed to be the same anyway)
62+ unsupportedChanges :: Changes
63+ , -- | forward and backward 'SchemaInvalid' (assumed to be the same anyway)
64+ schemaIssues :: Changes
6165 }
6266 deriving stock (Generic)
6367 deriving (Semigroup, Monoid) via (Generically ReportInput)
6468 deriving anyclass (ToJSON)
6569
6670segregateIssues :: CheckerOutput -> ReportInput
67−segregateIssues CheckerOutput {forwardChanges = fwd, backwardChanges = bck} = ReportInput
68− { breakingChanges = P.filter isBreaking fwd
69− , nonBreakingChanges = invertIssueOrientationP $ P.filterWithKey isNonBreaking bck
70− , unsupportedChanges = P.filter isUnsupported fwd <> P.filter isUnsupported bck
71− , schemaIssues = P.filter isSchemaIssue fwd <> P.filter isSchemaIssue bck
72− }
71+segregateIssues CheckerOutput {forwardChanges = fwd, backwardChanges = bck} =
72+ ReportInput
73+ { breakingChanges = P.filter isBreaking fwd
74+ , nonBreakingChanges = invertIssueOrientationP $ P.filterWithKey isNonBreaking bck
75+ , unsupportedChanges = P.filter isUnsupported fwd <> P.filter isUnsupported bck
76+ , schemaIssues = P.filter isSchemaIssue fwd <> P.filter isSchemaIssue bck
77+ }
7378 where
7479 isBreaking i = anIssueKind i `elem` [CertainIssue, ProbablyIssue, Comment]
7580 isNonBreaking :: Paths Behave 'APILevel a -> AnIssue a -> Bool
@@ -104,10 +109,9 @@ data ReportTreeStyle = HeadersTreeStyle | FoldingBlockquotesTreeStyle
104109twoRowTable :: [(Inlines, Inlines)] -> Blocks
105110twoRowTable x = simpleTable (para . fst <$> x) [para . snd <$> x]
106111
107−generateReport :: ReportConfig -> ReportInput -> (Pandoc, ReportStatus)
112+generateReport :: ReportConfig -> ReportInput -> (Blocks, ReportStatus)
108113generateReport cfg inp =
109− let
110− schemaIssuesPresent = not $ P.null $ schemaIssues inp
114+ let schemaIssuesPresent = not $ P.null $ schemaIssues inp
111115 breakingChangesPresent = not $ P.null $ breakingChanges inp
112116 nonBreakingChangesPresent = not $ P.null $ nonBreakingChanges inp
113117 unsupportedChangesPresent = not $ P.null $ unsupportedChanges inp
@@ -116,46 +120,55 @@ generateReport cfg inp =
116120 OnlyErrors -> False
117121 builder = buildReport cfg
118122 report =
119− doc $
120− header 1 "Summary"
121− <> twoRowTable
122− (when'
123− schemaIssuesPresent
124− [ ( refOpt schemaIssuesPresent schemaIssuesId "❌ Schema issues"
125− , show' $ P.size $ schemaIssues inp
126− )
127− ]
128− ++
129− [ ( refOpt breakingChangesPresent breakingChangesId "⚠️ Breaking changes"
123+ header 1 "Summary"
124+ <> twoRowTable
125+ ( when'
126+ schemaIssuesPresent
127+ [
128+ ( refOpt schemaIssuesPresent schemaIssuesId "‼️ Schema issues"
129+ , show' $ P.size $ schemaIssues inp
130+ )
131+ ]
132+ ++ [
133+ ( refOpt breakingChangesPresent breakingChangesId "❌ Breaking changes"
130134 , show' $ P.size $ breakingChanges inp
131135 )
132136 ]
133− ++ when'
134− nonBreakingChangesShown
135− [ ( refOpt nonBreakingChangesPresent nonBreakingChangesId "🙆 Non-breaking changes"
136− , show' $ P.size $ nonBreakingChanges inp
137− )
138− ]
139− ++ [ ( refOpt unsupportedChangesPresent unsupportedChangesId "🤷 Unsupported feature changes"
140− , show' $ P.size $ unsupportedChanges inp
141− )
142− ])
143− <> when'
144− schemaIssuesPresent
145− (header 1 (anchor schemaIssuesId <> "❌ Schema issues")
146− <> builder (showErrs $ schemaIssues inp))
147− <> when'
148− breakingChangesPresent
149− (header 1 (anchor breakingChangesId <> "⚠️ Breaking changes")
150− <> builder (showErrs $ breakingChanges inp))
151− <> when'
152− (nonBreakingChangesPresent && nonBreakingChangesShown)
153− (header 1 (anchor nonBreakingChangesId <> "🙆 Non-breaking changes")
154− <> builder (showErrs $ nonBreakingChanges inp))
155− <> when'
156− unsupportedChangesPresent
157− (header 1 (anchor unsupportedChangesId <> "🤷 Unsupported feature changes")
158− <> builder (showErrs $ unsupportedChanges inp))
137+ ++ when'
138+ nonBreakingChangesShown
139+ [
140+ ( refOpt nonBreakingChangesPresent nonBreakingChangesId "⚠️ Non-breaking changes"
141+ , show' $ P.size $ nonBreakingChanges inp
142+ )
143+ ]
144+ ++ when'
145+ unsupportedChangesPresent
146+ [
147+ ( refOpt unsupportedChangesPresent unsupportedChangesId "❓ Unsupported feature changes"
148+ , show' $ P.size $ unsupportedChanges inp
149+ )
150+ ]
151+ )
152+ <> when'
153+ schemaIssuesPresent
154+ ( header 1 (anchor schemaIssuesId <> "‼️ Schema issues")
155+ <> builder (showErrs $ schemaIssues inp)
156+ )
157+ <> when'
158+ breakingChangesPresent
159+ ( header 1 (anchor breakingChangesId <> "❌ Breaking changes")
160+ <> builder (showErrs $ breakingChanges inp)
161+ )
162+ <> when'
163+ (nonBreakingChangesPresent && nonBreakingChangesShown)
164+ ( header 1 (anchor nonBreakingChangesId <> "⚠️ Non-breaking changes")
165+ <> builder (showErrs $ nonBreakingChanges inp)
166+ )
167+ <> when'
168+ unsupportedChangesPresent
169+ ( header 1 (anchor unsupportedChangesId <> "❓ Unsupported feature changes")
170+ <> builder (showErrs $ unsupportedChanges inp)
171+ )
159172 status =
160173 if
161174 | breakingChangesPresent -> BreakingChanges
@@ -185,13 +198,14 @@ showErrs x@(P.PathsPrefixNode currentIssues _) =
185198 let -- Extract this pattern if more cases like this arise
186199 ( removedPaths :: Maybe (Orientation, [Issue 'APILevel])
187200 , otherIssues :: Set (AnIssue a)
188− ) = case eqT @a @'APILevel of
201+ ) = case eqT @a @ 'APILevel of
189202 Just Refl
190203 | (S.toList -> p@((AnIssue ori _) : _), o) <-
191204 S.partition
192− (\((AnIssue _ u)) -> case u of
193− NoPathsMatched {} -> True
194− AllPathsFailed {} -> True)
205+ ( \((AnIssue _ u)) -> case u of
206+ NoPathsMatched {} -> True
207+ AllPathsFailed {} -> True
208+ )
195209 currentIssues ->
196210 let p' = p <&> (\(AnIssue _ i) -> i)
197211 in (Just (ori, p'), o)
@@ -202,9 +216,10 @@ showErrs x@(P.PathsPrefixNode currentIssues _) =
202216 paths = case removedPaths of
203217 Just (ori, ps) -> do
204218 singletonHeader
205− (case ori of
206− Forward -> "Removed paths"
207− Backward -> "Added paths")
219+ ( case ori of
220+ Forward -> "Removed paths"
221+ Backward -> "Added paths"
222+ )
208223 $ singletonBody $
209224 bulletList $
210225 ps <&> \case
@@ -240,60 +255,62 @@ jets =
240255 , constructReportJet $ \p@(AtPath _) op@(InOperation _) ->
241256 strong (describeBehavior op) <> " " <> describeBehavior p :: Inlines
242257 , constructReportJet $ \(WithStatusCode c) ResponsePayload PayloadSchema ->
243− "📱⬅️ JSON Response – " <> str (T.pack . show $ c) :: Inlines
244− , constructReportJet $ \InRequest InPayload PayloadSchema -> "📱➡️ JSON Request" :: Inlines
258+ "⬅️☁️ JSON Response – " <> str (T.pack . show $ c) :: Inlines
259+ , constructReportJet $ \InRequest InPayload PayloadSchema -> "➡️☁️ JSON Request" :: Inlines
245260 ]
246261 where
247262 unwrapReportJetResult :: ReportJetResult Behave x -> ReportJet' Behave x
248263 unwrapReportJetResult (Pure _) = error "There really shouldn't be any results here."
249264 unwrapReportJetResult (Free f) = f
250265
251− jsonPathJet
252− :: NonEmpty
253− ( Union
254− '[ Behave 'SchemaLevel 'TypedSchemaLevel
255− , Behave 'TypedSchemaLevel 'SchemaLevel
256− ]
257− )
258− -> Inlines
266+ jsonPathJet ::
267+ NonEmpty
268+ ( Union
269+ '[ Behave 'SchemaLevel 'TypedSchemaLevel
270+ , Behave 'TypedSchemaLevel 'SchemaLevel
271+ ]
272+ ) ->
273+ Inlines
259274 jsonPathJet x = code $ "$" <> showParts (NE.toList x)
260275 where
261− showParts
262− :: [ Union
263− '[ Behave 'SchemaLevel 'TypedSchemaLevel
264− , Behave 'TypedSchemaLevel 'SchemaLevel
265− ]
266− ]
267− -> Text
276+ showParts ::
277+ [ Union
278+ '[ Behave 'SchemaLevel 'TypedSchemaLevel
279+ , Behave 'TypedSchemaLevel 'SchemaLevel
280+ ]
281+ ] ->
282+ Text
268283 showParts [] = mempty
269284 showParts (SingletonUnion (OfType Object) : xs@((SingletonUnion (InProperty _)) : _)) = showParts xs
270285 showParts (SingletonUnion (OfType Object) : xs@((SingletonUnion InAdditionalProperty) : _)) = showParts xs
271286 showParts (SingletonUnion (OfType Array) : xs@(SingletonUnion InItems : _)) = showParts xs
272287 showParts (SingletonUnion (OfType Array) : xs@(SingletonUnion (InItem _) : _)) = showParts xs
273288 showParts (y : ys) =
274− ((\(OfType t) -> "(" <> describeJSONType t <> ")")
275− @@> (\case
276− InItems -> "[*]"
277− InItem i -> "[" <> T.pack (show i) <> "]"
278− InProperty p -> "." <> p
279− InAdditionalProperty -> ".*")
280− @@> typesExhausted)
289+ ( (\(OfType t) -> "(" <> describeJSONType t <> ")")
290+ @@> ( \case
291+ InItems -> "[*]"
292+ InItem i -> "[" <> T.pack (show i) <> "]"
293+ InProperty p -> "." <> p
294+ InAdditionalProperty -> ".*"
295+ )
296+ @@> typesExhausted
297+ )
281298 y
282299 <> showParts ys
283300
284−observeJetShowErrs
285− :: ReportJet' Behave (Maybe Inlines)
286− -> P.PathsPrefixTree Behave AnIssue a
287− -> (Report, P.PathsPrefixTree Behave AnIssue a)
301+observeJetShowErrs ::
302+ ReportJet' Behave (Maybe Inlines) ->
303+ P.PathsPrefixTree Behave AnIssue a ->
304+ (Report, P.PathsPrefixTree Behave AnIssue a)
288305observeJetShowErrs jet p = case observeJetShowErrs' jet p of
289306 Just m -> m
290307 Nothing -> (mempty, p)
291308
292−observeJetShowErrs'
293− :: forall a.
294− ReportJet' Behave (Maybe Inlines)
295− -> P.PathsPrefixTree Behave AnIssue a
296− -> Maybe (Report, P.PathsPrefixTree Behave AnIssue a)
309+observeJetShowErrs' ::
310+ forall a.
311+ ReportJet' Behave (Maybe Inlines) ->
312+ P.PathsPrefixTree Behave AnIssue a ->
313+ Maybe (Report, P.PathsPrefixTree Behave AnIssue a)
297314observeJetShowErrs' (ReportJet jet) (P.PathsPrefixNode currentIssues subIssues) =
298315 let results =
299316 subIssues >>= \(WrapTypeable (AStep m)) ->
@@ -301,21 +318,23 @@ observeJetShowErrs' (ReportJet jet) (P.PathsPrefixNode currentIssues subIssues)
301318 maybe (Left $ embed (step bhv) subErrs) Right . listToMaybe $
302319 jet @_ @_ @[] bhv
303320 & mapMaybe
304− (\case
305− Free jet' -> fmap (embed $ step bhv) <$> observeJetShowErrs' jet' subErrs
306− Pure (Just h) ->
307− if P.null subErrs
308− then Just mempty
309− else Just (singletonHeader h (showErrs subErrs), mempty)
310− Pure Nothing -> Nothing)
321+ ( \case
322+ Free jet' -> fmap (embed $ step bhv) <$> observeJetShowErrs' jet' subErrs
323+ Pure (Just h) ->
324+ if P.null subErrs
325+ then Just mempty
326+ else Just (singletonHeader h (showErrs subErrs), mempty)
327+ Pure Nothing -> Nothing
328+ )
311329 in (fmap . fmap) (PathsPrefixNode currentIssues mempty <>) $
312330 if any isRight results
313331 then
314332 Just $
315333 foldMap
316− (\case
317− Left e -> (mempty, e)
318− Right m -> m)
334+ ( \case
335+ Left e -> (mempty, e)
336+ Right m -> m
337+ )
319338 results
320339 else Nothing
321340
@@ -338,8 +357,9 @@ buildReport cfg = case treeStyle cfg of
338357 body rprt
339358 <> foldOMapWithKey
340359 (headers rprt)
341− (\k v ->
342− header level k <> subBuilder v)
360+ ( \k v ->
361+ header level k <> subBuilder v
362+ )
343363 where
344364 subBuilder = headerStyleBuilder (level + 1)
345365
@@ -348,16 +368,17 @@ buildReport cfg = case treeStyle cfg of
348368 body rprt
349369 <> foldOMapWithKey
350370 (headers rprt)
351− (\k v ->
352− if (OM.size . headers $ rprt) < 2
353− then para k <> blockQuote (subBuilder v)
354− else
355− rawHtml "<details>"
356− <> rawHtml "<summary>"
357− <> plain k
358− <> rawHtml "</summary>"
359− <> blockQuote (subBuilder v)
360− <> rawHtml "</details>")
371+ ( \k v ->
372+ if (OM.size . headers $ rprt) < 2
373+ then para k <> blockQuote (subBuilder v)
374+ else
375+ rawHtml "<details>"
376+ <> rawHtml "<summary>"
377+ <> plain k
378+ <> rawHtml "</summary>"
379+ <> blockQuote (subBuilder v)
380+ <> rawHtml "</details>"
381+ )
361382 where
362383 subBuilder = foldingStyleBuilder
363384
src/OpenAPI/Checker/Report/Html/Template.hs → src/Data/OpenApi/Compare/Report/Html/Template.hsrenamed+4−4
@@ -1,7 +1,7 @@
11{-# LANGUAGE TemplateHaskell #-}
22
3−module OpenAPI.Checker.Report.Html.Template
4− ( template
3+module Data.OpenApi.Compare.Report.Html.Template
4+ ( template,
55 )
66where
77
@@ -24,11 +24,11 @@ template =
2424 \<meta charset=\"utf-8\">\
2525 \<title></title>\
2626 \<meta name=\"description\" content=\"\">\
27− \<meta name=\"generator\" content=\"OpenApi Diff\" />\
27+ \<meta name=\"generator\" content=\"CompaREST\" />\
2828 \<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=yes\" />\
2929 \</head>\
3030 \<body>\
31− \<header><h1>⛄ OpenApi Diff</h1></header>\
31+ \<header><h1>CompaREST</h1></header>\
3232 \<main>\
3333 \$body$\
3434 \</main>\
src/OpenAPI/Checker/Report/Jet.hs → src/Data/OpenApi/Compare/Report/Jet.hsrenamed+5−5
@@ -1,8 +1,8 @@
1−module OpenAPI.Checker.Report.Jet
2− ( ReportJet (..)
3− , ReportJet'
4− , ConstructReportJet (..)
5− , ReportJetResult
1+module Data.OpenApi.Compare.Report.Jet
2+ ( ReportJet (..),
3+ ReportJet',
4+ ConstructReportJet (..),
5+ ReportJetResult,
66 )
77where
88
src/OpenAPI/Checker/Run.hs → src/Data/OpenApi/Compare/Run.hsrenamed+10−9
@@ -1,16 +1,17 @@
1−module OpenAPI.Checker.Run
2− ( runChecker
3− , runReport
4− , module OpenAPI.Checker.Report
1+module Data.OpenApi.Compare.Run
2+ ( runChecker,
3+ runReport,
4+ module Data.OpenApi.Compare.Report,
55 )
66where
77
88import Data.HList
99import Data.OpenApi (OpenApi)
10−import OpenAPI.Checker.Paths
11−import OpenAPI.Checker.Report
12−import OpenAPI.Checker.Subtree
13−import OpenAPI.Checker.Validate.OpenApi ()
10+import Data.OpenApi.Compare.Paths
11+import Data.OpenApi.Compare.Report
12+import Data.OpenApi.Compare.Subtree
13+import Data.OpenApi.Compare.Validate.OpenApi ()
14+import Text.Pandoc.Builder
1415
1516runChecker :: (OpenApi, OpenApi) -> CheckerOutput
1617runChecker (client, server) =
@@ -26,5 +27,5 @@ runChecker (client, server) =
2627 }
2728 run p c = either id mempty . runCompatFormula . checkCompatibility Root HNil $ toPC p c
2829
29−runReport :: ReportConfig -> (OpenApi, OpenApi) -> (Pandoc, ReportStatus)
30+runReport :: ReportConfig -> (OpenApi, OpenApi) -> (Blocks, ReportStatus)
3031runReport cfg = generateReport cfg . segregateIssues . runChecker
src/OpenAPI/Checker/Subtree.hs → src/Data/OpenApi/Compare/Subtree.hsrenamed+128−126
@@ -1,52 +1,52 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Subtree
4− ( Steppable (..)
5− , Step (..)
6− , TraceRoot
7− , Trace
8− , Traced
9− , Traced'
10− , pattern Traced
11− , traced
12− , retraced
13− , stepTraced
14− , Subtree (..)
15− , checkCompatibility
16− , checkSubstructure
17− , CompatM (..)
18− , CompatFormula'
19− , SemanticCompatFormula
20− , ProdCons (..)
21− , orientProdCons
22− , swapProdCons
23− , runCompatFormula
24− , issueAt
25− , anItem
26− , anIssue
27− , invertIssueOrientation
28− , invertIssueOrientationP
29− , embedFormula
30− , anyOfAt
31− , clarifyIssue
32− , structuralIssue
3+module Data.OpenApi.Compare.Subtree
4+ ( Steppable (..),
5+ Step (..),
6+ TraceRoot,
7+ Trace,
8+ Traced,
9+ Traced',
10+ pattern Traced,
11+ traced,
12+ retraced,
13+ stepTraced,
14+ Subtree (..),
15+ checkCompatibility,
16+ checkSubstructure,
17+ CompatM (..),
18+ CompatFormula',
19+ SemanticCompatFormula,
20+ ProdCons (..),
21+ orientProdCons,
22+ swapProdCons,
23+ runCompatFormula,
24+ issueAt,
25+ anItem,
26+ anIssue,
27+ invertIssueOrientation,
28+ invertIssueOrientationP,
29+ embedFormula,
30+ anyOfAt,
31+ clarifyIssue,
32+ structuralIssue,
3333
3434 -- * Structural helpers
35− , structuralMaybe
36− , structuralMaybeWith
37− , structuralEq
38− , iohmStructural
39− , iohmStructuralWith
40− , structuralList
35+ structuralMaybe,
36+ structuralMaybeWith,
37+ structuralEq,
38+ iohmStructural,
39+ iohmStructuralWith,
40+ structuralList,
4141
4242 -- * Reexports
43− , (>>>)
44− , (<<<)
45− , extract
46− , ask
47− , local
48− , step
49− , Typeable
43+ (>>>),
44+ (<<<),
45+ extract,
46+ ask,
47+ local,
48+ step,
49+ Typeable,
5050 )
5151where
5252
@@ -60,13 +60,13 @@ import qualified Data.HashMap.Strict.InsOrd as IOHM
6060import Data.Hashable
6161import Data.Kind
6262import Data.OpenApi
63+import Data.OpenApi.Compare.Behavior
64+import Data.OpenApi.Compare.Formula
65+import Data.OpenApi.Compare.Memo
66+import Data.OpenApi.Compare.Paths
67+import qualified Data.OpenApi.Compare.PathsPrefixTree as P
6368import qualified Data.Set as S
6469import Data.Typeable
65−import OpenAPI.Checker.Behavior
66−import OpenAPI.Checker.Formula
67−import OpenAPI.Checker.Memo
68−import OpenAPI.Checker.Paths
69−import qualified OpenAPI.Checker.PathsPrefixTree as P
7070
7171class
7272 (Typeable Step, Typeable a, Typeable b, Ord (Step a b), Show (Step a b)) =>
@@ -115,10 +115,10 @@ orientProdCons :: Orientation -> ProdCons x -> ProdCons x
115115orientProdCons Forward x = x
116116orientProdCons Backward (ProdCons p c) = ProdCons c p
117117
118−swapProdCons
119− :: SwapEnvRoles xs
120− => (HList xs -> ProdCons x -> CompatFormula' q AnIssue r a)
121− -> (HList xs -> ProdCons x -> CompatFormula' q AnIssue r a)
118+swapProdCons ::
119+ SwapEnvRoles xs =>
120+ (HList xs -> ProdCons x -> CompatFormula' q AnIssue r a) ->
121+ (HList xs -> ProdCons x -> CompatFormula' q AnIssue r a)
122122swapProdCons f e (ProdCons p c) =
123123 invertIssueOrientation $
124124 f (swapEnvRoles e) (ProdCons c p)
@@ -153,8 +153,8 @@ instance Applicative ProdCons where
153153 ProdCons fp fc <*> ProdCons xp xc = ProdCons (fp xp) (fc xc)
154154
155155newtype CompatM a = CompatM
156− { unCompatM
157− :: StateT (MemoState VarRef) Identity a
156+ { unCompatM ::
157+ StateT (MemoState VarRef) Identity a
158158 }
159159 deriving newtype
160160 ( Functor
@@ -183,64 +183,64 @@ class (Typeable t, Issuable (SubtreeLevel t)) => Subtree (t :: Type) where
183183 type CheckEnv t :: [Type]
184184 type SubtreeLevel t :: BehaviorLevel
185185
186− checkStructuralCompatibility
187− :: HList (CheckEnv t)
188− -> ProdCons (Traced t)
189− -> StructuralCompatFormula ()
186+ checkStructuralCompatibility ::
187+ HList (CheckEnv t) ->
188+ ProdCons (Traced t) ->
189+ StructuralCompatFormula ()
190190
191− checkSemanticCompatibility
192− :: HList (CheckEnv t)
193− -> Behavior (SubtreeLevel t)
194− -> ProdCons (Traced t)
195− -> SemanticCompatFormula ()
191+ checkSemanticCompatibility ::
192+ HList (CheckEnv t) ->
193+ Behavior (SubtreeLevel t) ->
194+ ProdCons (Traced t) ->
195+ SemanticCompatFormula ()
196196
197197{-# WARNING checkStructuralCompatibility "You should not be calling this directly. Use 'checkSubstructure'" #-}
198198
199199{-# WARNING checkSemanticCompatibility "You should not be calling this directly. Use 'checkCompatibility'" #-}
200200
201−checkCompatibility
202− :: forall t xs.
203− (ReassembleHList xs (CheckEnv t), Subtree t)
204− => Behavior (SubtreeLevel t)
205− -> HList xs
206− -> ProdCons (Traced t)
207− -> SemanticCompatFormula ()
201+checkCompatibility ::
202+ forall t xs.
203+ (ReassembleHList xs (CheckEnv t), Subtree t) =>
204+ Behavior (SubtreeLevel t) ->
205+ HList xs ->
206+ ProdCons (Traced t) ->
207+ SemanticCompatFormula ()
208208checkCompatibility bhv e = memo bhv SemanticMemoKey $ \pc ->
209209 case runCompatFormula $ checkSubstructure e pc of
210210 Left _ -> checkSemanticCompatibility (reassemble e) bhv pc
211211 Right () -> pure ()
212212{-# INLINE checkCompatibility #-}
213213
214−checkSubstructure
215− :: (ReassembleHList xs (CheckEnv t), Subtree t)
216− => HList xs
217− -> ProdCons (Traced t)
218− -> StructuralCompatFormula ()
214+checkSubstructure ::
215+ (ReassembleHList xs (CheckEnv t), Subtree t) =>
216+ HList xs ->
217+ ProdCons (Traced t) ->
218+ StructuralCompatFormula ()
219219checkSubstructure e = memo Root StructuralMemoKey $ checkStructuralCompatibility (reassemble e)
220220{-# INLINE checkSubstructure #-}
221221
222−structuralMaybe
223− :: (Subtree a, ReassembleHList xs (CheckEnv a))
224− => HList xs
225− -> ProdCons (Maybe (Traced a))
226− -> StructuralCompatFormula ()
222+structuralMaybe ::
223+ (Subtree a, ReassembleHList xs (CheckEnv a)) =>
224+ HList xs ->
225+ ProdCons (Maybe (Traced a)) ->
226+ StructuralCompatFormula ()
227227structuralMaybe e = structuralMaybeWith (checkSubstructure e)
228228{-# INLINE structuralMaybe #-}
229229
230−structuralMaybeWith
231− :: (ProdCons a -> StructuralCompatFormula ())
232− -> ProdCons (Maybe a)
233− -> StructuralCompatFormula ()
230+structuralMaybeWith ::
231+ (ProdCons a -> StructuralCompatFormula ()) ->
232+ ProdCons (Maybe a) ->
233+ StructuralCompatFormula ()
234234structuralMaybeWith f (ProdCons (Just a) (Just b)) = f $ ProdCons a b
235235structuralMaybeWith _ (ProdCons Nothing Nothing) = pure ()
236236structuralMaybeWith _ _ = structuralIssue
237237{-# INLINE structuralMaybeWith #-}
238238
239−structuralList
240− :: (Subtree a, ReassembleHList xs (CheckEnv a))
241− => HList xs
242− -> ProdCons [Traced a]
243− -> StructuralCompatFormula ()
239+structuralList ::
240+ (Subtree a, ReassembleHList xs (CheckEnv a)) =>
241+ HList xs ->
242+ ProdCons [Traced a] ->
243+ StructuralCompatFormula ()
244244structuralList _ (ProdCons [] []) = pure ()
245245structuralList e (ProdCons (a : aa) (b : bb)) = do
246246 checkSubstructure e $ ProdCons a b
@@ -253,11 +253,11 @@ structuralEq :: (Eq a, Comonad w) => ProdCons (w a) -> StructuralCompatFormula (
253253structuralEq (ProdCons a b) = if extract a == extract b then pure () else structuralIssue
254254{-# INLINE structuralEq #-}
255255
256−iohmStructural
257− :: (ReassembleHList (k ': xs) (CheckEnv v), Ord k, Subtree v, Hashable k, Typeable k, Show k)
258− => HList xs
259− -> ProdCons (Traced (IOHM.InsOrdHashMap k v))
260− -> StructuralCompatFormula ()
256+iohmStructural ::
257+ (ReassembleHList (k ': xs) (CheckEnv v), Ord k, Subtree v, Hashable k, Typeable k, Show k) =>
258+ HList xs ->
259+ ProdCons (Traced (IOHM.InsOrdHashMap k v)) ->
260+ StructuralCompatFormula ()
261261iohmStructural e =
262262 iohmStructuralWith (\k -> checkSubstructure (k `HCons` e))
263263{-# INLINE iohmStructural #-}
@@ -266,25 +266,26 @@ instance (Typeable k, Typeable v, Ord k, Show k) => Steppable (IOHM.InsOrdHashMa
266266 data Step (IOHM.InsOrdHashMap k v) v = InsOrdHashMapKeyStep k
267267 deriving stock (Eq, Ord, Show)
268268
269−iohmStructuralWith
270− :: (Ord k, Hashable k, Typeable k, Typeable v, Show k)
271− => (k -> ProdCons (Traced v) -> StructuralCompatFormula ())
272− -> ProdCons (Traced (IOHM.InsOrdHashMap k v))
273− -> StructuralCompatFormula ()
269+iohmStructuralWith ::
270+ (Ord k, Hashable k, Typeable k, Typeable v, Show k) =>
271+ (k -> ProdCons (Traced v) -> StructuralCompatFormula ()) ->
272+ ProdCons (Traced (IOHM.InsOrdHashMap k v)) ->
273+ StructuralCompatFormula ()
274274iohmStructuralWith f pc = do
275275 let ProdCons pEKeys cEKeys = S.fromList . IOHM.keys . extract <$> pc
276276 if pEKeys == cEKeys
277277 then
278278 for_
279279 pEKeys
280− (\eKey ->
281− f eKey $ stepTraced (InsOrdHashMapKeyStep eKey) . fmap (IOHM.lookupDefault (error "impossible") eKey) <$> pc)
280+ ( \eKey ->
281+ f eKey $ stepTraced (InsOrdHashMapKeyStep eKey) . fmap (IOHM.lookupDefault (error "impossible") eKey) <$> pc
282+ )
282283 else structuralIssue
283284{-# INLINE iohmStructuralWith #-}
284285
285−runCompatFormula
286− :: CompatFormula' q f r a
287− -> Either (P.PathsPrefixTree q f r) a
286+runCompatFormula ::
287+ CompatFormula' q f r a ->
288+ Either (P.PathsPrefixTree q f r) a
288289runCompatFormula (Compose f) =
289290 calculate . runIdentity . runMemo 0 . unCompatM $ f
290291{-# INLINE runCompatFormula #-}
@@ -314,27 +315,27 @@ invertIssueOrientationP = P.map (\(AnIssue ori i) -> AnIssue (toggleOrientation
314315structuralIssue :: StructuralCompatFormula a
315316structuralIssue = Compose $ pure $ anError $ AnItem Root Proxy
316317
317−anyOfAt
318− :: Issuable l
319− => Paths q r l
320− -> Issue l
321− -> [CompatFormula' q AnIssue r a]
322− -> CompatFormula' q AnIssue r a
318+anyOfAt ::
319+ Issuable l =>
320+ Paths q r l ->
321+ Issue l ->
322+ [CompatFormula' q AnIssue r a] ->
323+ CompatFormula' q AnIssue r a
323324anyOfAt _ _ [x] = x
324325anyOfAt xs issue fs =
325326 Compose $ (`eitherOf` AnItem xs (anIssue issue)) <$> traverse getCompose fs
326327
327328-- | If the given formula contains any issues, add another issue on top. Otherwise succeed.
328−clarifyIssue
329− :: AnItem q AnIssue r
330− -> CompatFormula' q AnIssue r a
331− -> CompatFormula' q AnIssue r a
329+clarifyIssue ::
330+ AnItem q AnIssue r ->
331+ CompatFormula' q AnIssue r a ->
332+ CompatFormula' q AnIssue r a
332333clarifyIssue item f =
333334 Compose ((`eitherOf` item) <$> pure <$> getCompose f) *> f
334335
335−fixpointKnot
336− :: MonadState (MemoState VarRef) m
337− => KnotTier (FormulaF q f r ()) VarRef m
336+fixpointKnot ::
337+ MonadState (MemoState VarRef) m =>
338+ KnotTier (FormulaF q f r ()) VarRef m
338339fixpointKnot =
339340 KnotTier
340341 { onKnotFound = modifyMemoNonce succ
@@ -342,19 +343,20 @@ fixpointKnot =
342343 , tieKnot = \i x -> pure $ maxFixpoint i x
343344 }
344345
345−memo
346− :: (Typeable (l :: k), Typeable q, Typeable f, Typeable k, Typeable a)
347− => Paths q r l
348− -> MemoKey
349− -> (ProdCons (Traced a) -> CompatFormula' q f r ())
350− -> (ProdCons (Traced a) -> CompatFormula' q f r ())
346+memo ::
347+ (Typeable (l :: k), Typeable q, Typeable f, Typeable k, Typeable a) =>
348+ Paths q r l ->
349+ MemoKey ->
350+ (ProdCons (Traced a) -> CompatFormula' q f r ()) ->
351+ (ProdCons (Traced a) -> CompatFormula' q f r ())
351352memo bhv k f pc = Compose $ do
352353 formula' <-
353354 memoWithKnot
354355 fixpointKnot
355− (do
356− formula <- getCompose $ f pc
357− pure $ mapErrors (P.takeSubtree bhv) formula)
356+ ( do
357+ formula <- getCompose $ f pc
358+ pure $ mapErrors (P.takeSubtree bhv) formula
359+ )
358360 (k, ask <$> pc)
359361 pure $ mapErrors (P.embed bhv) formula'
360362
src/OpenAPI/Checker/Validate/Header.hs → src/Data/OpenApi/Compare/Validate/Header.hsrenamed+5−5
@@ -1,6 +1,6 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Validate.Header
3+module Data.OpenApi.Compare.Validate.Header
44 (
55 )
66where
@@ -9,10 +9,10 @@ import Data.Foldable
99import Data.Functor
1010import Data.Maybe
1111import Data.OpenApi
12−import OpenAPI.Checker.Behavior
13−import OpenAPI.Checker.References ()
14−import OpenAPI.Checker.Subtree
15−import OpenAPI.Checker.Validate.Schema ()
12+import Data.OpenApi.Compare.Behavior
13+import Data.OpenApi.Compare.References ()
14+import Data.OpenApi.Compare.Subtree
15+import Data.OpenApi.Compare.Validate.Schema ()
1616import Text.Pandoc.Builder
1717
1818instance Subtree Header where
src/OpenAPI/Checker/Validate/Link.hs → src/Data/OpenApi/Compare/Validate/Link.hsrenamed+4−4
@@ -1,10 +1,10 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Validate.Link () where
3+module Data.OpenApi.Compare.Validate.Link () where
44
55import Data.OpenApi
6−import OpenAPI.Checker.Behavior
7−import OpenAPI.Checker.Subtree
6+import Data.OpenApi.Compare.Behavior
7+import Data.OpenApi.Compare.Subtree
88import Text.Pandoc.Builder
99
1010instance Subtree Link where
@@ -19,4 +19,4 @@ instance Issuable 'LinkLevel where
1919 deriving stock (Eq, Ord, Show)
2020 issueKind = \case
2121 LinksUnsupported -> Unsupported
22− describeIssue _ LinksUnsupported = para "OpenApi Diff does not currently support Link Objects."
22+ describeIssue _ LinksUnsupported = para "CompaREST does not currently support Link Objects."
src/OpenAPI/Checker/Validate/MediaTypeObject.hs → src/Data/OpenApi/Compare/Validate/MediaTypeObject.hsrenamed+10−10
@@ -1,8 +1,8 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Validate.MediaTypeObject
4− ( Issue (..)
5− , Behave (..)
3+module Data.OpenApi.Compare.Validate.MediaTypeObject
4+ ( Issue (..),
5+ Behave (..),
66 )
77where
88
@@ -12,15 +12,15 @@ import Data.HList
1212import Data.HashMap.Strict.InsOrd as IOHM
1313import Data.Map.Strict as M
1414import Data.OpenApi
15+import Data.OpenApi.Compare.Behavior
16+import Data.OpenApi.Compare.Subtree
17+import Data.OpenApi.Compare.Validate.Header ()
18+import Data.OpenApi.Compare.Validate.Products
19+import Data.OpenApi.Compare.Validate.Schema ()
1520import Data.String
1621import Data.Text (Text)
1722import qualified Data.Text as T
1823import Network.HTTP.Media (MediaType, mainType, subType)
19−import OpenAPI.Checker.Behavior
20−import OpenAPI.Checker.Subtree
21−import OpenAPI.Checker.Validate.Header ()
22−import OpenAPI.Checker.Validate.Products
23−import OpenAPI.Checker.Validate.Schema ()
2424import Text.Pandoc.Builder
2525
2626tracedSchema :: Traced MediaTypeObject -> Maybe (Traced (Referenced Schema))
@@ -45,7 +45,7 @@ instance Issuable 'PayloadLevel where
4545 describeIssue _ MediaTypeSchemaRequired = para "Media type expected, but was not specified."
4646 describeIssue Forward (MediaEncodingMissing enc) = para $ "Media encoding " <> str enc <> " has been removed."
4747 describeIssue Backward (MediaEncodingMissing enc) = para $ "Media encoding " <> str enc <> " added."
48− describeIssue _ EncodingNotSupported = para "OpenApi Diff does not currently support media encodings other than JSON."
48+ describeIssue _ EncodingNotSupported = para "CompaREST does not currently support media encodings other than JSON."
4949
5050instance Behavable 'PayloadLevel 'SchemaLevel where
5151 data Behave 'PayloadLevel 'SchemaLevel
@@ -119,7 +119,7 @@ instance Subtree Encoding where
119119
120120 -- FIXME: Support only JSON body for now. Then Encoding is checked only for
121121 -- multipart/form-url-encoded
122− -- https://github.com/typeable/openapi-diff/issues/54
122+ -- #54
123123 checkSemanticCompatibility _env beh _pc =
124124 issueAt beh EncodingNotSupported
125125
src/OpenAPI/Checker/Validate/OAuth2Flows.hs → src/Data/OpenApi/Compare/Validate/OAuth2Flows.hsrenamed+17−17
@@ -1,9 +1,9 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Validate.OAuth2Flows
4− ( Step (..)
5− , Issue (..)
6− , Behave (..)
3+module Data.OpenApi.Compare.Validate.OAuth2Flows
4+ ( Step (..),
5+ Issue (..),
6+ Behave (..),
77 )
88where
99
@@ -12,13 +12,13 @@ import Data.Function
1212import Data.Functor
1313import qualified Data.HashMap.Strict.InsOrd as IOHM
1414import Data.OpenApi
15+import Data.OpenApi.Compare.Behavior
16+import Data.OpenApi.Compare.Orphans ()
17+import Data.OpenApi.Compare.Subtree
1518import Data.Proxy
1619import Data.Set (Set)
1720import qualified Data.Set as S
1821import Data.Text (Text)
19−import OpenAPI.Checker.Behavior
20−import OpenAPI.Checker.Orphans ()
21−import OpenAPI.Checker.Subtree
2222import Text.Pandoc.Builder
2323
2424instance Subtree OAuth2Flows where
@@ -26,11 +26,11 @@ instance Subtree OAuth2Flows where
2626 type SubtreeLevel OAuth2Flows = 'SecuritySchemeLevel
2727 checkStructuralCompatibility _ = structuralEq
2828 checkSemanticCompatibility env bhv pc = do
29− let supportFlow
30− :: (Subtree t, SubtreeLevel t ~ SubtreeLevel OAuth2Flows, CheckEnv OAuth2Flows ~ CheckEnv t)
31− => Issue 'SecuritySchemeLevel
32− -> ProdCons (Maybe (Traced t))
33− -> SemanticCompatFormula ()
29+ let supportFlow ::
30+ (Subtree t, SubtreeLevel t ~ SubtreeLevel OAuth2Flows, CheckEnv OAuth2Flows ~ CheckEnv t) =>
31+ Issue 'SecuritySchemeLevel ->
32+ ProdCons (Maybe (Traced t)) ->
33+ SemanticCompatFormula ()
3434 supportFlow i x = case x of
3535 -- producer will not attempt this flow
3636 (ProdCons Nothing _) -> pure ()
@@ -38,11 +38,11 @@ instance Subtree OAuth2Flows where
3838 (ProdCons (Just _) Nothing) -> issueAt bhv i
3939 (ProdCons (Just p) (Just c)) ->
4040 checkCompatibility bhv env $ ProdCons p c
41− getFlow
42− :: Typeable x
43− => (OAuth2Flows -> Maybe (OAuth2Flow x))
44− -> Traced OAuth2Flows
45− -> Maybe (Traced (OAuth2Flow x))
41+ getFlow ::
42+ Typeable x =>
43+ (OAuth2Flows -> Maybe (OAuth2Flow x)) ->
44+ Traced OAuth2Flows ->
45+ Maybe (Traced (OAuth2Flow x))
4646 getFlow f (Traced t a) = Traced (t >>> step (OAuth2FlowsFlow Proxy)) <$> f a
4747 supportFlow ConsumerDoesNotSupportImplicitFlow $ getFlow _oAuth2FlowsImplicit <$> pc
4848 supportFlow ConsumerDoesNotSupportPasswordFlow $ getFlow _oAuth2FlowsPassword <$> pc
src/OpenAPI/Checker/Validate/OpenApi.hs → src/Data/OpenApi/Compare/Validate/OpenApi.hsrenamed+18−17
@@ -2,19 +2,19 @@
22-- Does not compiles otherwise
33{-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-}
44
5−module OpenAPI.Checker.Validate.OpenApi
6− ( Behave (..)
7− , Issue (..)
5+module Data.OpenApi.Compare.Validate.OpenApi
6+ ( Behave (..),
7+ Issue (..),
88 )
99where
1010
1111import Data.HList
1212import qualified Data.HashMap.Strict.InsOrd as IOHM
1313import Data.OpenApi
14−import OpenAPI.Checker.Behavior
15−import OpenAPI.Checker.Paths
16−import OpenAPI.Checker.Subtree
17−import OpenAPI.Checker.Validate.Operation
14+import Data.OpenApi.Compare.Behavior
15+import Data.OpenApi.Compare.Paths
16+import Data.OpenApi.Compare.Subtree
17+import Data.OpenApi.Compare.Validate.Operation
1818
1919tracedPaths :: Traced OpenApi -> Traced ProcessedPathItems
2020tracedPaths oa =
@@ -79,16 +79,17 @@ instance Subtree OpenApi where
7979 checkSemanticCompatibility _ beh prodCons = do
8080 checkCompatibility @ProcessedPathItems
8181 beh
82− ((tracedRequestBodies <$> prodCons)
83− `HCons` (tracedParameters <$> prodCons)
84− `HCons` (tracedSecuritySchemes <$> prodCons)
85− `HCons` (tracedResponses <$> prodCons)
86− `HCons` (tracedHeaders <$> prodCons)
87− `HCons` (tracedSchemas <$> prodCons)
88− `HCons` (_openApiServers . extract <$> prodCons)
89− `HCons` (tracedLinks <$> prodCons)
90− `HCons` (tracedCallbacks <$> prodCons)
91− `HCons` HNil)
82+ ( (tracedRequestBodies <$> prodCons)
83+ `HCons` (tracedParameters <$> prodCons)
84+ `HCons` (tracedSecuritySchemes <$> prodCons)
85+ `HCons` (tracedResponses <$> prodCons)
86+ `HCons` (tracedHeaders <$> prodCons)
87+ `HCons` (tracedSchemas <$> prodCons)
88+ `HCons` (_openApiServers . extract <$> prodCons)
89+ `HCons` (tracedLinks <$> prodCons)
90+ `HCons` (tracedCallbacks <$> prodCons)
91+ `HCons` HNil
92+ )
9293 (tracedPaths <$> prodCons)
9394
9495instance Steppable OpenApi ProcessedPathItems where
src/OpenAPI/Checker/Validate/Operation.hs → src/Data/OpenApi/Compare/Validate/Operation.hsrenamed+43−41
@@ -2,19 +2,19 @@
22{-# OPTIONS_GHC -Wno-orphans #-}
33{-# OPTIONS_GHC -fconstraint-solver-iterations=0 #-}
44
5−module OpenAPI.Checker.Validate.Operation
5+module Data.OpenApi.Compare.Validate.Operation
66 ( -- * Operation
7− MatchedOperation (..)
8− , OperationMethod (..)
9− , pathItemMethod
7+ MatchedOperation (..),
8+ OperationMethod (..),
9+ pathItemMethod,
1010
1111 -- * ProcessedPathItem
12− , ProcessedPathItem (..)
13− , ProcessedPathItems (..)
14− , processPathItems
15− , Step (..)
16− , Behave (..)
17− , Issue (..)
12+ ProcessedPathItem (..),
13+ ProcessedPathItems (..),
14+ processPathItems,
15+ Step (..),
16+ Behave (..),
17+ Issue (..),
1818 )
1919where
2020
@@ -29,21 +29,21 @@ import qualified Data.List as L
2929import Data.Map.Strict as M
3030import Data.Maybe
3131import Data.OpenApi
32+import Data.OpenApi.Compare.Behavior
33+import Data.OpenApi.Compare.References
34+import Data.OpenApi.Compare.Subtree
35+import Data.OpenApi.Compare.Validate.MediaTypeObject
36+import Data.OpenApi.Compare.Validate.OAuth2Flows
37+import Data.OpenApi.Compare.Validate.PathFragment
38+import Data.OpenApi.Compare.Validate.Products
39+import Data.OpenApi.Compare.Validate.RequestBody
40+import Data.OpenApi.Compare.Validate.Responses
41+import Data.OpenApi.Compare.Validate.SecurityRequirement ()
42+import Data.OpenApi.Compare.Validate.Server ()
43+import Data.OpenApi.Compare.Validate.Sums
3244import Data.String
3345import Data.Text (Text)
3446import qualified Data.Text as T
35−import OpenAPI.Checker.Behavior
36−import OpenAPI.Checker.References
37−import OpenAPI.Checker.Subtree
38−import OpenAPI.Checker.Validate.MediaTypeObject
39−import OpenAPI.Checker.Validate.OAuth2Flows
40−import OpenAPI.Checker.Validate.PathFragment
41−import OpenAPI.Checker.Validate.Products
42−import OpenAPI.Checker.Validate.RequestBody
43−import OpenAPI.Checker.Validate.Responses
44−import OpenAPI.Checker.Validate.SecurityRequirement ()
45−import OpenAPI.Checker.Validate.Server ()
46−import OpenAPI.Checker.Validate.Sums
4747import Text.Pandoc.Builder
4848
4949data MatchedOperation = MatchedOperation
@@ -86,11 +86,12 @@ tracedCallbacks (Traced t oper) =
8686 | (k, v) <- IOHM.toList . _operationCallbacks . operation $ oper
8787 ]
8888
89−-- FIXME: https://github.com/typeable/openapi-diff/issues/28
90−getServers
91− :: [Server] -- ^ Servers from env
92− -> MatchedOperation
93− -> [Server]
89+-- FIXME: #28
90+getServers ::
91+ -- | Servers from env
92+ [Server] ->
93+ MatchedOperation ->
94+ [Server]
9495getServers env oper =
9596 case _operationServers . operation $ oper of
9697 [] -> env
@@ -241,7 +242,7 @@ instance Subtree MatchedOperation where
241242 checkProducts beh (const NoRequestBody) (const check) elements
242243 checkResponses =
243244 swapProdCons (checkCompatibility beh) env $ tracedResponses <$> prodCons
244− -- FIXME: https://github.com/typeable/openapi-diff/issues/27
245+ -- FIXME: #27
245246 checkCallbacks = do
246247 let ProdCons pCallbacks cCallbacks = tracedCallbacks <$> prodCons
247248 for_ pCallbacks $ \(k, pCallback) -> do
@@ -250,7 +251,7 @@ instance Subtree MatchedOperation where
250251 cCallbacks <&> \(_, cCallback) -> do
251252 swapProdCons (checkCompatibility beh') env $ ProdCons pCallback cCallback
252253 pure ()
253− -- FIXME: https://github.com/typeable/openapi-diff/issues/28
254+ -- FIXME: #28
254255 checkOperationSecurity = do
255256 let ProdCons pSecs cSecs = tracedSecurity <$> prodCons
256257 for_ pSecs $ \(i, pSec) -> do
@@ -337,9 +338,10 @@ instance Issuable 'APILevel where
337338 deriving stock (Eq, Ord, Show)
338339 issueKind = \case
339340 _ -> CertainIssue
340− relatedIssues = (==) `withClass` \case
341− NoPathsMatched fp -> Just fp
342− AllPathsFailed fp -> Just fp
341+ relatedIssues =
342+ (==) `withClass` \case
343+ NoPathsMatched fp -> Just fp
344+ AllPathsFailed fp -> Just fp
343345 describeIssue Forward (NoPathsMatched p) = para $ "The path " <> (code . T.pack) p <> " has been removed."
344346 describeIssue Backward (NoPathsMatched p) = para $ "The path " <> (code . T.pack) p <> " has been added."
345347 describeIssue Forward (AllPathsFailed p) = para $ "The path " <> (code . T.pack) p <> " has been removed."
@@ -434,10 +436,10 @@ tracedFragments mpi =
434436 | (i, x) <- L.zip [0 ..] $ pathFragments $ extract mpi
435437 ]
436438
437−tracedMethod
438− :: OperationMethod
439− -> Traced MatchedPathItem
440− -> Maybe (Traced' MatchedOperation Operation)
439+tracedMethod ::
440+ OperationMethod ->
441+ Traced MatchedPathItem ->
442+ Maybe (Traced' MatchedOperation Operation)
441443tracedMethod s mpi = env (ask mpi >>> step (OperationMethodStep s)) <$> (pathItemMethod s . pathItem . extract $ mpi)
442444
443445instance Issuable 'PathLevel where
@@ -485,10 +487,10 @@ instance Subtree MatchedPathItem where
485487 checkSemanticCompatibility env beh prodCons = do
486488 let paramDefs = getH @(ProdCons (Traced (Definitions Param))) env
487489 pathTracedParams = getPathParams <$> paramDefs <*> prodCons
488− getPathParams
489− :: Traced (Definitions Param)
490− -> Traced MatchedPathItem
491− -> [Traced Param]
490+ getPathParams ::
491+ Traced (Definitions Param) ->
492+ Traced MatchedPathItem ->
493+ [Traced Param]
492494 getPathParams defs mpi = do
493495 paramRef <- tracedMatchedPathItemParameters mpi
494496 pure $ dereference defs paramRef
@@ -564,7 +566,7 @@ instance Issuable 'CallbackLevel where
564566 deriving stock (Eq, Ord, Show)
565567 issueKind = \case
566568 CallbacksUnsupported -> Unsupported
567− describeIssue _ CallbacksUnsupported = para "OpenApi Diff does not currently support callbacks."
569+ describeIssue _ CallbacksUnsupported = para "CompaREST does not currently support callbacks."
568570
569571tracedCallbackPathItems :: Traced Callback -> Traced ProcessedPathItems
570572tracedCallbackPathItems (Traced t (Callback x)) =
src/OpenAPI/Checker/Validate/Param.hs → src/Data/OpenApi/Compare/Validate/Param.hsrenamed+14−12
@@ -1,9 +1,9 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
33
4−module OpenAPI.Checker.Validate.Param
5− ( Behave (..)
6− , Issue (..)
4+module Data.OpenApi.Compare.Validate.Param
5+ ( Behave (..),
6+ Issue (..),
77 )
88where
99
@@ -11,11 +11,11 @@ import Control.Monad
1111import Data.Functor
1212import Data.Maybe
1313import Data.OpenApi
14+import Data.OpenApi.Compare.Behavior
15+import Data.OpenApi.Compare.Orphans ()
16+import Data.OpenApi.Compare.Subtree
17+import Data.OpenApi.Compare.Validate.Schema ()
1418import Data.Text as T
15−import OpenAPI.Checker.Behavior
16−import OpenAPI.Checker.Orphans ()
17−import OpenAPI.Checker.Subtree
18−import OpenAPI.Checker.Validate.Schema ()
1919import Text.Pandoc.Builder
2020
2121-- | The type is normalized encoding style of the parameter. If two encoding
@@ -79,7 +79,7 @@ instance Issuable 'PathFragmentLevel where
7979 describeIssue _ ParamPlaceIncompatible = para "Parameters in incompatible locations."
8080 describeIssue _ ParamStyleMismatch = para "Different parameter styles (encodings)."
8181 describeIssue _ ParamSchemaMismatch = para "Expected a schema, but didn't find one."
82− describeIssue ori (PathFragmentsDontMatch (orientProdCons ori -> ProdCons e a)) =
82+ describeIssue ori (PathFragmentsDontMatch (orientProdCons ori -> ProdCons e a)) =
8383 para $ "Parameter changed from " <> code e <> " to " <> code a <> "."
8484
8585instance Behavable 'PathFragmentLevel 'SchemaLevel where
@@ -106,15 +106,17 @@ instance Subtree Param where
106106 when (_paramName (extract p) /= _paramName (extract c)) $
107107 issueAt beh ParamNameMismatch
108108 when
109− ((fromMaybe False . _paramRequired . extract $ c)
110− && not (fromMaybe False . _paramRequired . extract $ p))
109+ ( (fromMaybe False . _paramRequired . extract $ c)
110+ && not (fromMaybe False . _paramRequired . extract $ p)
111+ )
111112 $ issueAt beh ParamRequired
112113 case (_paramIn . extract $ p, _paramIn . extract $ c) of
113114 (ParamQuery, ParamQuery) -> do
114115 -- Emptiness is only for query params
115116 when
116− ((fromMaybe False . _paramAllowEmptyValue . extract $ p)
117− && not (fromMaybe False . _paramAllowEmptyValue . extract $ c))
117+ ( (fromMaybe False . _paramAllowEmptyValue . extract $ p)
118+ && not (fromMaybe False . _paramAllowEmptyValue . extract $ c)
119+ )
118120 $ issueAt beh ParamEmptinessIncompatible
119121 (a, b) | a == b -> pure ()
120122 _ -> issueAt beh ParamPlaceIncompatible
src/OpenAPI/Checker/Validate/PathFragment.hs → src/Data/OpenApi/Compare/Validate/PathFragment.hsrenamed+8−9
@@ -1,21 +1,20 @@
1−module OpenAPI.Checker.Validate.PathFragment
2− ( parsePath
3− , PathFragment (..)
4− , PathFragmentParam
1+module Data.OpenApi.Compare.Validate.PathFragment
2+ ( parsePath,
3+ PathFragment (..),
4+ PathFragmentParam,
55 )
66where
77
88import qualified Data.Aeson as A
99import Data.OpenApi
10+import Data.OpenApi.Compare.Behavior
11+import Data.OpenApi.Compare.Subtree
12+import Data.OpenApi.Compare.Validate.Param
1013import Data.Text (Text)
1114import qualified Data.Text as T
12−import Data.Typeable
13−import OpenAPI.Checker.Behavior
14−import OpenAPI.Checker.Subtree
15−import OpenAPI.Checker.Validate.Param
1615
1716-- TODO: templates can be only part of the PathFragment. Currently only supports templates as full PathFragment.
18−-- https://github.com/typeable/openapi-diff/issues/23
17+-- #23
1918parsePath :: FilePath -> [PathFragment Text]
2019parsePath = fmap partition . T.splitOn "/" . T.pack
2120 where
src/Data/OpenApi/Compare/Validate/Products.hsnew file+48
@@ -0,0 +1,48 @@
1+-- | Checks product-like entities. The key is some identificator for the product
2+--element. Each element may be required or optional.
3+--
4+--One example of product is request parameters. There are optional and required
5+--parameters. The client and server have possibly different set of
6+--parameters. What we must check is if server requires some request parameter,
7+--then this parameter must be presented by client and their schemas must match.
8+--
9+--So when we checking products we are checking from the server's (consumer)
10+--perspective, ensuring that all parameters are provided by the client (producer)
11+--and their schemas match.
12+--
13+--This module abstracts this logic for arbitrary elements
14+module Data.OpenApi.Compare.Validate.Products
15+ ( checkProducts,
16+ ProductLike (..),
17+ )
18+where
19+
20+import Data.Foldable
21+import Data.Map.Strict (Map)
22+import qualified Data.Map.Strict as M
23+import Data.OpenApi.Compare.Behavior
24+import Data.OpenApi.Compare.Paths
25+import Data.OpenApi.Compare.Subtree
26+
27+-- | Some entity which is product-like
28+data ProductLike a = ProductLike
29+ { productValue :: a
30+ , required :: Bool
31+ }
32+
33+checkProducts ::
34+ (Ord k, Issuable l) =>
35+ Paths q r l ->
36+ -- | No required element found
37+ (k -> Issue l) ->
38+ (k -> ProdCons t -> CompatFormula' q AnIssue r ()) ->
39+ ProdCons (Map k (ProductLike t)) ->
40+ CompatFormula' q AnIssue r ()
41+checkProducts xs noElt check (ProdCons p c) = for_ (M.toList c) $ \(key, consElt) ->
42+ case M.lookup key p of
43+ Nothing -> case required consElt of
44+ True -> issueAt xs $ noElt key
45+ False -> pure ()
46+ Just prodElt -> do
47+ let elts = ProdCons prodElt consElt
48+ check key (productValue <$> elts)
src/OpenAPI/Checker/Validate/RequestBody.hs → src/Data/OpenApi/Compare/Validate/RequestBody.hsrenamed+7−7
@@ -1,8 +1,8 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Validate.RequestBody
4− ( Issue (..)
5− , Behave (..)
3+module Data.OpenApi.Compare.Validate.RequestBody
4+ ( Issue (..),
5+ Behave (..),
66 )
77where
88
@@ -11,12 +11,12 @@ import Data.HashMap.Strict.InsOrd as IOHM
1111import Data.Map.Strict as M
1212import Data.Maybe
1313import Data.OpenApi
14+import Data.OpenApi.Compare.Behavior
15+import Data.OpenApi.Compare.Subtree
16+import Data.OpenApi.Compare.Validate.MediaTypeObject
17+import Data.OpenApi.Compare.Validate.Sums
1418import qualified Data.Text as T
1519import Network.HTTP.Media (MediaType)
16−import OpenAPI.Checker.Behavior
17−import OpenAPI.Checker.Subtree
18−import OpenAPI.Checker.Validate.MediaTypeObject
19−import OpenAPI.Checker.Validate.Sums
2020import Text.Pandoc.Builder
2121
2222-- TODO: Use RequestMediaTypeObjectMapping
src/OpenAPI/Checker/Validate/Responses.hs → src/Data/OpenApi/Compare/Validate/Responses.hsrenamed+11−11
@@ -1,8 +1,8 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
33
4−module OpenAPI.Checker.Validate.Responses
5− ( Behave (..)
4+module Data.OpenApi.Compare.Validate.Responses
5+ ( Behave (..),
66 )
77where
88
@@ -11,17 +11,17 @@ import Data.HashMap.Strict.InsOrd as IOHM
1111import Data.Map.Strict as M
1212import Data.Maybe
1313import Data.OpenApi
14+import Data.OpenApi.Compare.Behavior
15+import Data.OpenApi.Compare.References
16+import Data.OpenApi.Compare.Subtree
17+import Data.OpenApi.Compare.Validate.Header ()
18+import Data.OpenApi.Compare.Validate.Link ()
19+import Data.OpenApi.Compare.Validate.MediaTypeObject
20+import Data.OpenApi.Compare.Validate.Products
21+import Data.OpenApi.Compare.Validate.Schema ()
22+import Data.OpenApi.Compare.Validate.Sums
1423import qualified Data.Text as T
1524import Network.HTTP.Media (MediaType)
16−import OpenAPI.Checker.Behavior
17−import OpenAPI.Checker.References
18−import OpenAPI.Checker.Subtree
19−import OpenAPI.Checker.Validate.Header ()
20−import OpenAPI.Checker.Validate.Link ()
21−import OpenAPI.Checker.Validate.MediaTypeObject
22−import OpenAPI.Checker.Validate.Products
23−import OpenAPI.Checker.Validate.Schema ()
24−import OpenAPI.Checker.Validate.Sums
2525import Text.Pandoc.Builder
2626
2727tracedResponses :: Traced Responses -> IOHM.InsOrdHashMap HttpStatusCode (Traced (Referenced Response))
src/OpenAPI/Checker/Validate/Schema.hs → src/Data/OpenApi/Compare/Validate/Schema.hsrenamed+95−115
@@ -1,5 +1,6 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
2−module OpenAPI.Checker.Validate.Schema
2+
3+module Data.OpenApi.Compare.Validate.Schema
34 (
45 )
56where
@@ -10,63 +11,64 @@ import Data.Coerce
1011import Data.Foldable (for_, toList)
1112import Data.Functor
1213import Data.HList
14+import Data.List (genericIndex, genericLength, group)
1315import qualified Data.List.NonEmpty as NE
1416import qualified Data.Map as M
15−import Data.List (group, genericLength, genericIndex)
1617import Data.Maybe
1718import Data.OpenApi
19+import Data.OpenApi.Compare.Behavior
20+import Data.OpenApi.Compare.Paths
21+import qualified Data.OpenApi.Compare.PathsPrefixTree as P
22+import Data.OpenApi.Compare.Subtree
23+import Data.OpenApi.Compare.Validate.Schema.DNF
24+import Data.OpenApi.Compare.Validate.Schema.Issues
25+import Data.OpenApi.Compare.Validate.Schema.JsonFormula
26+import Data.OpenApi.Compare.Validate.Schema.Partition
27+import Data.OpenApi.Compare.Validate.Schema.Process
28+import Data.OpenApi.Compare.Validate.Schema.Traced
29+import Data.OpenApi.Compare.Validate.Schema.TypedJson
1830import Data.Ord
1931import Data.Ratio
2032import Data.Semigroup
2133import qualified Data.Set as S
2234import Data.Text (Text)
23−import OpenAPI.Checker.Behavior
24−import OpenAPI.Checker.Paths
25−import qualified OpenAPI.Checker.PathsPrefixTree as P
26−import OpenAPI.Checker.Subtree
27−import OpenAPI.Checker.Validate.Schema.DNF
28−import OpenAPI.Checker.Validate.Schema.Issues
29−import OpenAPI.Checker.Validate.Schema.JsonFormula
30−import OpenAPI.Checker.Validate.Schema.Traced
31−import OpenAPI.Checker.Validate.Schema.TypedJson
32−import OpenAPI.Checker.Validate.Schema.Partition
33−import OpenAPI.Checker.Validate.Schema.Process
3435
35−checkFormulas
36− :: (ReassembleHList xs (CheckEnv (Referenced Schema)))
37− => HList xs
38− -> Behavior 'SchemaLevel
39− -> ProdCons (Trace Schema)
40− -> ProdCons (Traced (Definitions Schema))
41− -> ProdCons (ForeachType JsonFormula, P.PathsPrefixTree Behave AnIssue 'SchemaLevel)
42− -> SemanticCompatFormula ()
36+checkFormulas ::
37+ (ReassembleHList xs (CheckEnv (Referenced Schema))) =>
38+ HList xs ->
39+ Behavior 'SchemaLevel ->
40+ ProdCons (Trace Schema) ->
41+ ProdCons (Traced (Definitions Schema)) ->
42+ ProdCons (ForeachType JsonFormula, P.PathsPrefixTree Behave AnIssue 'SchemaLevel) ->
43+ SemanticCompatFormula ()
4344checkFormulas env beh trs defs (ProdCons (fp, ep) (fc, ec)) =
4445 case P.toList ep ++ P.toList ec of
4546 issues@(_ : _) -> for_ issues $ embedFormula beh . anItem
4647 [] -> do
4748 -- We have the following isomorphisms:
48− -- (A ⊂ X ∪ Y) = (A ⊂ X) \/ (A ⊂ Y)
4949 -- (A ⊂ X ∩ Y) = (A ⊂ X) /\ (A ⊂ Y)
5050 -- (A ⊂ ⊤) = 1
5151 -- (X ∪ Y ⊂ B) = (X ⊂ B) /\ (Y ⊂ B)
5252 -- (∅ ⊂ B) = 1
5353 -- The remaining cases are, notably, not isomorphisms:
54− -- 1) (A ⊂ ∅) <= 0
55− -- 2) (X ∩ Y ⊂ B) <= (X ⊂ B) \/ (Y ⊂ B)
56− -- 3) (⊤ ⊂ B) <= 0
57− -- Therefore we have the isomorphisms with (∃ and ∀ being the N-ary
54+ -- 1) (A ⊂ X ∪ Y) <= (A ⊂ X) \/ (A ⊂ Y)
55+ -- 2) (A ⊂ ∅) <= 0
56+ -- 3) (X ∩ Y ⊂ B) <= (X ⊂ B) \/ (Y ⊂ B)
57+ -- 4) (⊤ ⊂ B) <= 0
58+ -- Therefore we have the implications with (∃ and ∀ being the N-ary
5859 -- versions of \/ and /\ respectively):
5960 -- (⋃_i ⋂_j A[i,j]) ⊂ (⋃_k ⋂_l B[k,l])
60− -- = ∃k ∀l ∀i, (⋂_j A[i,j]) ⊂ B[k,l]
61+ -- <= ∃k ∀l ∀i, (⋂_j A[i,j]) ⊂ B[k,l]
6162 -- = ∀i ∃k ∀l, (⋂_j A[i,j]) ⊂ B[k,l]
6263 -- with the caveat that the the set over which k ranges is nonempty.
63− -- This is because 1) is not an isomorphism.
64+ -- (because 2) is not an isomorphism), and that this is a sufficient
65+ -- but not necessary condition (because 1) is not an isomorphism).
6466 -- Our disjunction loses information, so it makes sense to nest it as
6567 -- deeply as possible, hence we choose the latter representation.
6668 --
6769 -- We delegate the verification of (⋂_j A[j]) ⊂ B to a separate heuristic
6870 -- function, with the understanding that ∃j, A[j] ⊂ B is a sufficient,
69− -- but not necessary condition (because of 2) and 3)).
71+ -- but not necessary condition (because of 3) and 4)).
7072 --
7173 -- If k ranges over an empty set, we have the isomorphism:
7274 -- (⋃_i ⋂_j A[i,j]) ⊂ ∅ = ∀i, (⋂_j A[i,j]) ⊂ ∅
@@ -143,21 +145,21 @@ checkFormulas env beh trs defs (ProdCons (fp, ep) (fc, ec)) =
143145 EnumDoesntSatisfy $ untypeValue e -- what does this look like when partitioned?
144146 issueFromDisjunct mPart ps = NoMatchingCondition mPart $ SomeCondition <$> S.toList ps
145147
146−checkContradiction
147− :: Behavior 'TypedSchemaLevel
148− -> Maybe Partition
149− -> S.Set (Condition t)
150− -> SemanticCompatFormula ()
148+checkContradiction ::
149+ Behavior 'TypedSchemaLevel ->
150+ Maybe Partition ->
151+ S.Set (Condition t) ->
152+ SemanticCompatFormula ()
151153checkContradiction beh mPart _ = issueAt beh $ maybe TypeBecomesEmpty PartitionBecomesEmpty mPart -- TODO #70
152154
153−checkImplication
154− :: (ReassembleHList xs (CheckEnv (Referenced Schema)))
155− => HList xs
156− -> Behavior 'TypedSchemaLevel
157− -> ProdCons (Trace Schema) -- the traces of the root schemas used in this comparison
158− -> S.Set (Condition t)
159− -> Condition t
160− -> SemanticCompatFormula ()
155+checkImplication ::
156+ (ReassembleHList xs (CheckEnv (Referenced Schema))) =>
157+ HList xs ->
158+ Behavior 'TypedSchemaLevel ->
159+ ProdCons (Trace Schema) -> -- the traces of the root schemas used in this comparison
160+ S.Set (Condition t) ->
161+ Condition t ->
162+ SemanticCompatFormula ()
161163checkImplication env beh trs prods cons = case findExactly prods of
162164 Just e
163165 | all (satisfiesTyped e) prods ->
@@ -165,124 +167,104 @@ checkImplication env beh trs prods cons = case findExactly prods of
165167 then pure ()
166168 else issueAt beh (EnumDoesntSatisfy $ untypeValue e)
167169 | otherwise -> pure () -- vacuously true
168−
169170 Nothing -> case cons of
170171 -- the above code didn't catch it, so there's no Exactly condition on the lhs
171172 Exactly e -> issueAt beh (NoMatchingEnum $ untypeValue e)
172−
173173 Maximum m -> foldCheck min m NoMatchingMaximum MatchingMaximumWeak $ \case
174174 Maximum m' -> Just m'
175175 _ -> Nothing
176−
177176 Minimum m -> foldCheck max m (NoMatchingMinimum . coerce) (MatchingMinimumWeak . coerce) $ \case
178177 Minimum m' -> Just m'
179178 _ -> Nothing
180−
181179 MultipleOf m -> foldCheck lcmScientific m NoMatchingMultipleOf MatchingMultipleOfWeak $ \case
182180 MultipleOf m' -> Just m'
183181 _ -> Nothing
184−
185182 NumberFormat f -> case flip any prods $ \case
186− NumberFormat f' -> f == f'
187− _ -> False
188− of
183+ NumberFormat f' -> f == f'
184+ _ -> False of
189185 True -> pure ()
190186 False -> issueAt beh (NoMatchingFormat f)
191−
192187 MaxLength m -> foldCheck min m NoMatchingMaxLength MatchingMaxLengthWeak $ \case
193188 MaxLength m' -> Just m'
194189 _ -> Nothing
195−
196190 MinLength m -> foldCheck max m NoMatchingMinLength MatchingMinLengthWeak $ \case
197191 MinLength m' -> Just m'
198192 _ -> Nothing
199−
200193 Pattern p -> case flip any prods $ \case
201− Pattern p' -> p == p'
202− _ -> False
203− of
194+ Pattern p' -> p == p'
195+ _ -> False of
204196 True -> pure ()
205197 False -> issueAt beh (NoMatchingPattern p) -- TODO: regex comparison #32
206−
207198 StringFormat f -> case flip any prods $ \case
208− StringFormat f' -> f == f'
209− _ -> False
210− of
199+ StringFormat f' -> f == f'
200+ _ -> False of
211201 True -> pure ()
212202 False -> issueAt beh (NoMatchingFormat f)
213−
214203 Items _ cons' -> case foldSome (<>) prods $ \case
215− Items _ rs -> Just (Just (rs NE.:| []), mempty)
216− TupleItems (map snd -> fs) -> Just (mempty, Just (fs NE.:| []))
217− _ -> Nothing
218− of
204+ Items _ rs -> Just (Just (rs NE.:| []), mempty)
205+ TupleItems (map snd -> fs) -> Just (mempty, Just (fs NE.:| []))
206+ _ -> Nothing of
219207 Just (mItems, Just pfs)
220208 | not $ allSame (length <$> pfs) -> pure () -- vacuously
221− | let plen = genericLength (NE.head pfs)
222− -> clarifyIssue (AnItem beh (anIssue TupleToArray)) $ for_ [0 .. plen - 1] $ \i -> do
223− let prod' = tracedConjunct $ case mItems of
224− Just prods' -> ((`genericIndex` i) <$> pfs) <> prods'
225− Nothing -> (`genericIndex` i) <$> pfs
226− checkCompatibility (beh >>> step (InItem i)) env $ ProdCons prod' cons'
209+ | let plen = genericLength (NE.head pfs) ->
210+ clarifyIssue (AnItem beh (anIssue TupleToArray)) $
211+ for_ [0 .. plen - 1] $ \i -> do
212+ let prod' = tracedConjunct $ case mItems of
213+ Just prods' -> ((`genericIndex` i) <$> pfs) <> prods'
214+ Nothing -> (`genericIndex` i) <$> pfs
215+ checkCompatibility (beh >>> step (InItem i)) env $ ProdCons prod' cons'
227216 Just (Just prods', Nothing) -> do
228217 let prod' = tracedConjunct prods'
229218 checkCompatibility (beh >>> step InItems) env $ ProdCons prod' cons'
230219 _ -> clarifyIssue (AnItem beh (anIssue NoMatchingItems)) $ do
231220 checkCompatibility (beh >>> step InItems) env $ ProdCons prodTopSchema cons'
232−
233221 TupleItems (map snd -> fs) -> case foldSome (<>) prods $ \case
234− TupleItems (map snd -> fs') -> Just (Just $ fs' NE.:| [], Just . Max $ genericLength fs', Just . Min $ genericLength fs', mempty)
235− MinItems m' -> Just (mempty, Just . Max $ m', mempty, mempty)
236− MaxItems m' -> Just (mempty, mempty, Just . Min $ m', mempty)
237− Items _ rs -> Just (mempty, mempty, mempty, Just (rs NE.:| []))
238− _ -> Nothing
239− of
240− -- if the length constraints in the producer are contradictory:
241− Just (_, Just (Max lowest), Just (Min highest), _) | lowest > highest -> pure ()
242− -- We have an explicit tuple items clause...
243− Just (Just pfs, Just (Max plen), _, _)
244− | plen /= genericLength fs -- ...of wrong length
245− -> issueAt beh (TupleItemsLengthChanged ProdCons {producer = plen, consumer = genericLength fs})
246− | otherwise
247− -> for_ [0 .. plen - 1] $ \i -> do
222+ TupleItems (map snd -> fs') -> Just (Just $ fs' NE.:| [], Just . Max $ genericLength fs', Just . Min $ genericLength fs', mempty)
223+ MinItems m' -> Just (mempty, Just . Max $ m', mempty, mempty)
224+ MaxItems m' -> Just (mempty, mempty, Just . Min $ m', mempty)
225+ Items _ rs -> Just (mempty, mempty, mempty, Just (rs NE.:| []))
226+ _ -> Nothing of
227+ -- if the length constraints in the producer are contradictory:
228+ Just (_, Just (Max lowest), Just (Min highest), _) | lowest > highest -> pure ()
229+ -- We have an explicit tuple items clause...
230+ Just (Just pfs, Just (Max plen), _, _)
231+ | plen /= genericLength fs -> -- ...of wrong length
232+ issueAt beh (TupleItemsLengthChanged ProdCons {producer = plen, consumer = genericLength fs})
233+ | otherwise ->
234+ for_ [0 .. plen - 1] $ \i -> do
248235 checkCompatibility (beh >>> step (InItem i)) env $ ProdCons (tracedConjunct $ (`genericIndex` i) <$> pfs) (fs `genericIndex` i)
249− -- We have a fixed length array in the producer...
250− Just (Nothing, Just (Max plen), Just (Min plen'), mProd)
251− | plen == plen'
252− -> clarifyIssue (AnItem beh (anIssue ArrayToTuple)) $ case mProd of
253− _ | plen /= genericLength fs -> -- ...of wrong length
254− issueAt beh (TupleItemsLengthChanged ProdCons {producer = plen, consumer = genericLength fs})
236+ -- We have a fixed length array in the producer...
237+ Just (Nothing, Just (Max plen), Just (Min plen'), mProd)
238+ | plen == plen' ->
239+ clarifyIssue (AnItem beh (anIssue ArrayToTuple)) $ case mProd of
240+ _
241+ | plen /= genericLength fs -> -- ...of wrong length
242+ issueAt beh (TupleItemsLengthChanged ProdCons {producer = plen, consumer = genericLength fs})
255243 Just rs -> for_ [0 .. plen - 1] $ \i -> do
256244 checkCompatibility (beh >>> step (InItem i)) env $ ProdCons (tracedConjunct rs) (fs `genericIndex` i)
257245 -- ...and no "items" schema
258246 Nothing -> clarifyIssue (AnItem beh (anIssue NoMatchingTupleItems)) $ do
259247 for_ [0 .. plen - 1] $ \i -> do
260248 checkCompatibility (beh >>> step (InItem i)) env $ ProdCons prodTopSchema (fs `genericIndex` i)
261− _ -> issueAt beh NoMatchingTupleItems
262−
249+ _ -> issueAt beh NoMatchingTupleItems
263250 MaxItems m -> foldCheck min m NoMatchingMaxItems MatchingMaxItemsWeak $ \case
264251 MaxItems m' -> Just m'
265252 TupleItems fs -> Just $ toInteger $ length fs
266253 _ -> Nothing
267−
268254 MinItems m -> foldCheck max m NoMatchingMinItems MatchingMinItemsWeak $ \case
269255 MinItems m' -> Just m'
270256 TupleItems fs -> Just $ toInteger $ length fs
271257 _ -> Nothing
272−
273258 UniqueItems -> case flip any prods $ \case
274− UniqueItems -> True
275− MaxItems 1 -> True
276− TupleItems fs | length fs == 1 -> True
277− _ -> False
278− of
259+ UniqueItems -> True
260+ MaxItems 1 -> True
261+ TupleItems fs | length fs == 1 -> True
262+ _ -> False of
279263 True -> pure ()
280264 False -> issueAt beh NoMatchingUniqueItems
281−
282265 Properties props _ madd -> case foldSome (<>) prods $ \case
283− Properties props' _ madd' -> Just $ (props', madd') NE.:| []
284− _ -> Nothing
285− of
266+ Properties props' _ madd' -> Just $ (props', madd') NE.:| []
267+ _ -> Nothing of
286268 Just pm ->
287269 anyOfAt beh NoMatchingProperties $ -- TODO: could first "concat" the lists
288270 NE.toList pm <&> \(props', madd') -> do
@@ -313,11 +295,9 @@ checkImplication env beh trs prods cons = case findExactly prods of
313295 (Just add', Just add) -> checkCompatibility (beh >>> step InAdditionalProperty) env (ProdCons add' add)
314296 pure ()
315297 Nothing -> issueAt beh NoMatchingProperties
316−
317298 MaxProperties m -> foldCheck min m NoMatchingMaxProperties MatchingMaxPropertiesWeak $ \case
318299 MaxProperties m' -> Just m'
319300 _ -> Nothing
320−
321301 MinProperties m -> foldCheck max m NoMatchingMinProperties MatchingMinPropertiesWeak $ \case
322302 MinProperties m' -> Just m'
323303 _ -> Nothing
@@ -325,14 +305,14 @@ checkImplication env beh trs prods cons = case findExactly prods of
325305 lcmScientific (toRational -> a) (toRational -> b) =
326306 fromRational $ lcm (numerator a) (numerator b) % gcd (denominator a) (denominator b)
327307
328− foldCheck
329− :: Eq a
330− => (a -> a -> a)
331− -> a
332− -> (a -> Issue 'TypedSchemaLevel)
333− -> (ProdCons a -> Issue 'TypedSchemaLevel)
334− -> (forall t. Condition t -> Maybe a)
335− -> SemanticCompatFormula ()
308+ foldCheck ::
309+ Eq a =>
310+ (a -> a -> a) ->
311+ a ->
312+ (a -> Issue 'TypedSchemaLevel) ->
313+ (ProdCons a -> Issue 'TypedSchemaLevel) ->
314+ (forall t. Condition t -> Maybe a) ->
315+ SemanticCompatFormula ()
336316 foldCheck f m missing weak extr = case foldSome f prods extr of
337317 Just m'
338318 | f m' m == m' -> pure ()
src/OpenAPI/Checker/Validate/Schema/DNF.hs → src/Data/OpenApi/Compare/Validate/Schema/DNF.hsrenamed+9−9
@@ -1,12 +1,12 @@
1−module OpenAPI.Checker.Validate.Schema.DNF
2− ( DNF (..)
3− , Disjunct (..)
4− , pattern SingleDisjunct
5− , pattern TopDNF
6− , pattern BottomDNF
7− , pattern LiteralDNF
8− , foldDNF
9− , forDNF
1+module Data.OpenApi.Compare.Validate.Schema.DNF
2+ ( DNF (..),
3+ Disjunct (..),
4+ pattern SingleDisjunct,
5+ pattern TopDNF,
6+ pattern BottomDNF,
7+ pattern LiteralDNF,
8+ foldDNF,
9+ forDNF,
1010 )
1111where
1212
src/OpenAPI/Checker/Validate/Schema/Issues.hs → src/Data/OpenApi/Compare/Validate/Schema/Issues.hsrenamed+61−59
@@ -1,21 +1,21 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Validate.Schema.Issues
4− ( Issue (..)
5− , Behave (..)
3+module Data.OpenApi.Compare.Validate.Schema.Issues
4+ ( Issue (..),
5+ Behave (..),
66 )
77where
88
99import qualified Data.Aeson as A
1010import Data.OpenApi
11+import Data.OpenApi.Compare.Behavior
12+import Data.OpenApi.Compare.Subtree
13+import Data.OpenApi.Compare.Validate.Schema.JsonFormula
14+import Data.OpenApi.Compare.Validate.Schema.Partition
15+import Data.OpenApi.Compare.Validate.Schema.TypedJson
1116import Data.Scientific
1217import Data.Text (Text)
1318import qualified Data.Text as T
14−import OpenAPI.Checker.Behavior
15−import OpenAPI.Checker.Subtree
16−import OpenAPI.Checker.Validate.Schema.JsonFormula
17−import OpenAPI.Checker.Validate.Schema.Partition
18−import OpenAPI.Checker.Validate.Schema.TypedJson
1919import Text.Pandoc.Builder hiding (Format, Null)
2020
2121instance Issuable 'TypedSchemaLevel where
@@ -100,53 +100,54 @@ instance Issuable 'TypedSchemaLevel where
100100 TypeBecomesEmpty -> ProbablyIssue -- TODO: #70
101101 PartitionBecomesEmpty _ -> ProbablyIssue -- ditto
102102 _ -> CertainIssue
103− relatedIssues = (==) `withClass` \case
103+ relatedIssues =
104+ (==) `withClass` \case
104105 EnumDoesntSatisfy v -> Just v
105106 NoMatchingEnum v -> Just v
106107 _ -> Nothing
107− `withClass` \case
108− NoMatchingMaximum _ -> Just ()
109− MatchingMaximumWeak _ -> Just ()
110− _ -> Nothing
111− `withClass` \case
112− NoMatchingMinimum _ -> Just ()
113− MatchingMinimumWeak _ -> Just ()
114− _ -> Nothing
115− `withClass` \case
116− NoMatchingMultipleOf _ -> Just ()
117− MatchingMultipleOfWeak _ -> Just ()
118− _ -> Nothing
119− `withClass` \case
120− NoMatchingMaxLength _ -> Just ()
121− MatchingMaxLengthWeak _ -> Just ()
122− _ -> Nothing
123− `withClass` \case
124− NoMatchingMinLength _ -> Just ()
125− MatchingMinLengthWeak _ -> Just ()
126− _ -> Nothing
127− `withClass` \case
128− NoMatchingItems -> Just ()
129− ArrayToTuple -> Just ()
130− TupleToArray -> Just ()
131− NoMatchingTupleItems -> Just ()
132− TupleItemsLengthChanged _ -> Just ()
133− _ -> Nothing
134− `withClass` \case
135− NoMatchingMaxItems _ -> Just ()
136− MatchingMaxItemsWeak _ -> Just ()
137− _ -> Nothing
138− `withClass` \case
139− NoMatchingMinItems _ -> Just ()
140− MatchingMinItemsWeak _ -> Just ()
141− _ -> Nothing
142− `withClass` \case
143− NoMatchingMaxProperties _ -> Just ()
144− MatchingMaxPropertiesWeak _ -> Just ()
145− _ -> Nothing
146− `withClass` \case
147− NoMatchingMinProperties _ -> Just ()
148− MatchingMinPropertiesWeak _ -> Just ()
149− _ -> Nothing
108+ `withClass` \case
109+ NoMatchingMaximum _ -> Just ()
110+ MatchingMaximumWeak _ -> Just ()
111+ _ -> Nothing
112+ `withClass` \case
113+ NoMatchingMinimum _ -> Just ()
114+ MatchingMinimumWeak _ -> Just ()
115+ _ -> Nothing
116+ `withClass` \case
117+ NoMatchingMultipleOf _ -> Just ()
118+ MatchingMultipleOfWeak _ -> Just ()
119+ _ -> Nothing
120+ `withClass` \case
121+ NoMatchingMaxLength _ -> Just ()
122+ MatchingMaxLengthWeak _ -> Just ()
123+ _ -> Nothing
124+ `withClass` \case
125+ NoMatchingMinLength _ -> Just ()
126+ MatchingMinLengthWeak _ -> Just ()
127+ _ -> Nothing
128+ `withClass` \case
129+ NoMatchingItems -> Just ()
130+ ArrayToTuple -> Just ()
131+ TupleToArray -> Just ()
132+ NoMatchingTupleItems -> Just ()
133+ TupleItemsLengthChanged _ -> Just ()
134+ _ -> Nothing
135+ `withClass` \case
136+ NoMatchingMaxItems _ -> Just ()
137+ MatchingMaxItemsWeak _ -> Just ()
138+ _ -> Nothing
139+ `withClass` \case
140+ NoMatchingMinItems _ -> Just ()
141+ MatchingMinItemsWeak _ -> Just ()
142+ _ -> Nothing
143+ `withClass` \case
144+ NoMatchingMaxProperties _ -> Just ()
145+ MatchingMaxPropertiesWeak _ -> Just ()
146+ _ -> Nothing
147+ `withClass` \case
148+ NoMatchingMinProperties _ -> Just ()
149+ MatchingMinPropertiesWeak _ -> Just ()
150+ _ -> Nothing
150151 describeIssue Forward (EnumDoesntSatisfy v) = para "The following enum value was removed:" <> showJSONValue v
151152 describeIssue Backward (EnumDoesntSatisfy v) = para "The following enum value was added:" <> showJSONValue v
152153 describeIssue Forward (NoMatchingEnum v) = para "The following enum value has been added:" <> showJSONValue v
@@ -254,16 +255,17 @@ instance Issuable 'SchemaLevel where
254255 PropertyToAdditional -> Comment
255256 TypesRestricted _ -> ProbablyIssue -- TODO: #70
256257 _ -> CertainIssue
257− relatedIssues = (==) `withClass` \case
258+ relatedIssues =
259+ (==) `withClass` \case
258260 AdditionalToProperty -> Just ()
259261 PropertyToAdditional -> Just ()
260262 _ -> Nothing
261− `withClass` \case
262− PropertyNowRequired -> Just ()
263− UnexpectedProperty -> Just ()
264− _ -> Nothing
263+ `withClass` \case
264+ PropertyNowRequired -> Just ()
265+ UnexpectedProperty -> Just ()
266+ _ -> Nothing
265267 describeIssue _ (NotSupported i) =
266− para (emph "Encountered a feature that OpenApi Diff does not support: " <> text i <> ".")
268+ para (emph "Encountered a feature that CompaREST does not support: " <> text i <> ".")
267269 describeIssue _ OneOfNotDisjoint =
268270 para $
269271 "Could not deduce that " <> code "oneOf"
@@ -275,7 +277,7 @@ instance Issuable 'SchemaLevel where
275277 describeIssue _ (InvalidSchema i) =
276278 para (emph "The schema is invalid: " <> text i <> ".")
277279 describeIssue _ UnguardedRecursion =
278− para "Encountered recursion that is too complex for OpenApi Diff to untangle."
280+ para "Encountered recursion that is too complex for CompaREST to untangle."
279281 describeIssue Forward (TypesRestricted tys) = case tys of
280282 [] -> para "No longer has any valid values." -- weird
281283 _ -> para "Values are now limited to the following types: " <> bulletList (para . describeJSONType <$> tys)
src/OpenAPI/Checker/Validate/Schema/JsonFormula.hs → src/Data/OpenApi/Compare/Validate/Schema/JsonFormula.hsrenamed+55−48
@@ -1,18 +1,18 @@
1−module OpenAPI.Checker.Validate.Schema.JsonFormula
2− ( Bound (..)
3− , showBound
4− , Property (..)
5− , Condition (..)
6− , showCondition
7− , satisfiesTyped
8− , checkStringFormat
9− , checkNumberFormat
10− , SomeCondition (..)
11− , JsonFormula (..)
12− , satisfiesFormula
13− , satisfies
14− , showJSONValue
15− , showJSONValueInline
1+module Data.OpenApi.Compare.Validate.Schema.JsonFormula
2+ ( Bound (..),
3+ showBound,
4+ Property (..),
5+ Condition (..),
6+ showCondition,
7+ satisfiesTyped,
8+ checkStringFormat,
9+ checkNumberFormat,
10+ SomeCondition (..),
11+ JsonFormula (..),
12+ satisfiesFormula,
13+ satisfies,
14+ showJSONValue,
15+ showJSONValueInline,
1616 )
1717where
1818
@@ -20,13 +20,17 @@ import Algebra.Lattice
2020import qualified Data.Aeson as A
2121import qualified Data.ByteString.Lazy as BSL
2222import qualified Data.Foldable as F
23−import qualified Data.HashMap.Strict as HM
2423import Data.Functor
24+import qualified Data.HashMap.Strict as HM
2525import Data.Int
2626import Data.Kind
2727import qualified Data.Map as M
2828import Data.Maybe
2929import Data.OpenApi
30+import Data.OpenApi.Compare.Orphans ()
31+import Data.OpenApi.Compare.Subtree
32+import Data.OpenApi.Compare.Validate.Schema.DNF
33+import Data.OpenApi.Compare.Validate.Schema.TypedJson
3034import Data.Ord
3135import Data.Ratio
3236import Data.Scientific
@@ -35,10 +39,6 @@ import Data.Text (Text)
3539import qualified Data.Text as T
3640import qualified Data.Text.Encoding as T
3741import Data.Typeable
38−import OpenAPI.Checker.Orphans ()
39−import OpenAPI.Checker.Subtree
40−import OpenAPI.Checker.Validate.Schema.DNF
41−import OpenAPI.Checker.Validate.Schema.TypedJson
4242import Text.Pandoc.Builder hiding (Format, Null)
4343import Text.Regex.Pcre2
4444
@@ -67,30 +67,33 @@ data Property = Property
6767data Condition :: JsonType -> Type where
6868 Exactly :: TypedValue t -> Condition t
6969 Maximum :: !(Bound Scientific) -> Condition 'Number
70− Minimum
71− :: !(Down (Bound (Down Scientific)))
72− -> Condition 'Number -- ^ this has the right Ord
70+ Minimum ::
71+ !(Down (Bound (Down Scientific))) ->
72+ -- | this has the right Ord
73+ Condition 'Number
7374 MultipleOf :: !Scientific -> Condition 'Number
7475 NumberFormat :: !Format -> Condition 'Number
7576 MaxLength :: !Integer -> Condition 'String
7677 MinLength :: !Integer -> Condition 'String
7778 Pattern :: !Pattern -> Condition 'String
7879 StringFormat :: !Format -> Condition 'String
79− Items
80− :: !(ForeachType JsonFormula)
81− -> !(Traced (Referenced Schema))
82− -> Condition 'Array
83− TupleItems
84− :: ![(ForeachType JsonFormula, Traced (Referenced Schema))]
85− -> Condition 'Array
80+ Items ::
81+ !(ForeachType JsonFormula) ->
82+ !(Traced (Referenced Schema)) ->
83+ Condition 'Array
84+ TupleItems ::
85+ ![(ForeachType JsonFormula, Traced (Referenced Schema))] ->
86+ Condition 'Array
8687 MaxItems :: !Integer -> Condition 'Array
8788 MinItems :: !Integer -> Condition 'Array
8889 UniqueItems :: Condition 'Array
89− Properties
90− :: !(M.Map Text Property)
91− -> !(ForeachType JsonFormula) -- ^ formula for additional properties
92− -> !(Maybe (Traced (Referenced Schema))) -- ^ schema for additional properties, Nothing means bottom
93− -> Condition 'Object
90+ Properties ::
91+ !(M.Map Text Property) ->
92+ -- | formula for additional properties
93+ !(ForeachType JsonFormula) ->
94+ -- | schema for additional properties, Nothing means bottom
95+ !(Maybe (Traced (Referenced Schema))) ->
96+ Condition 'Object
9497 MaxProperties :: !Integer -> Condition 'Object
9598 MinProperties :: !Integer -> Condition 'Object
9699
@@ -118,13 +121,15 @@ showCondition = \case
118121 UniqueItems -> para "The elements in the array should be unique."
119122 (Properties props additional _) ->
120123 bulletList $
121− (M.toList props
122− <&> (\(k, p) ->
123− para (code k)
124− <> para (strong $ if propRequired p then "Required" else "Optional")
125− <> showForEachJsonFormula (propFormula p)))
124+ ( M.toList props
125+ <&> ( \(k, p) ->
126+ para (code k)
127+ <> para (strong $ if propRequired p then "Required" else "Optional")
128+ <> showForEachJsonFormula (propFormula p)
129+ )
130+ )
126131 <> [ para (emph "Additional properties")
127− <> showForEachJsonFormula additional
132+ <> showForEachJsonFormula additional
128133 ]
129134 (MaxProperties n) -> para $ "The maximum number of fields should be " <> show' n <> "."
130135 (MinProperties n) -> para $ "The minimum number of fields should be " <> show' n <> "."
@@ -133,15 +138,17 @@ showCondition = \case
133138 showForEachJsonFormula i =
134139 bulletList $
135140 foldType
136− (\t f -> case getJsonFormula $ f i of
137− BottomDNF -> mempty
138− (DNF conds) ->
141+ ( \t f -> case getJsonFormula $ f i of
142+ BottomDNF -> mempty
143+ (DNF conds) ->
139144 [ para (describeJSONType t)
140145 <> bulletList
141− (S.toList conds <&> \case
142− Disjunct (S.toList -> []) -> para "Empty"
143− Disjunct (S.toList -> cond) -> bulletList (showCondition <$> cond))
144− ])
146+ ( S.toList conds <&> \case
147+ Disjunct (S.toList -> []) -> para "Empty"
148+ Disjunct (S.toList -> cond) -> bulletList (showCondition <$> cond)
149+ )
150+ ]
151+ )
145152
146153showJSONValue :: A.Value -> Blocks
147154showJSONValue v = codeBlockWith ("", ["json"], mempty) (T.decodeUtf8 . BSL.toStrict . A.encode $ v)
src/OpenAPI/Checker/Validate/Schema/Partition.hs → src/Data/OpenApi/Compare/Validate/Schema/Partition.hsrenamed+38−37
@@ -1,15 +1,15 @@
1−module OpenAPI.Checker.Validate.Schema.Partition
2− ( partitionSchema
3− , partitionRefSchema
4− , selectPartition
5− , runPartitionM
6− , tryPartition
7− , showPartition
8− , intersectSchema
9− , intersectRefSchema
10− , IntersectionResult (..)
11− , runIntersectionM
12− , Partition
1+module Data.OpenApi.Compare.Validate.Schema.Partition
2+ ( partitionSchema,
3+ partitionRefSchema,
4+ selectPartition,
5+ runPartitionM,
6+ tryPartition,
7+ showPartition,
8+ intersectSchema,
9+ intersectRefSchema,
10+ IntersectionResult (..),
11+ runIntersectionM,
12+ Partition,
1313 )
1414where
1515
@@ -32,17 +32,17 @@ import qualified Data.List.NonEmpty as NE
3232import qualified Data.Map as M
3333import Data.Maybe
3434import Data.OpenApi
35+import Data.OpenApi.Compare.Memo
36+import Data.OpenApi.Compare.References
37+import Data.OpenApi.Compare.Subtree
38+import Data.OpenApi.Compare.Validate.Schema.DNF
39+import Data.OpenApi.Compare.Validate.Schema.JsonFormula
40+import Data.OpenApi.Compare.Validate.Schema.Traced
41+import Data.OpenApi.Compare.Validate.Schema.TypedJson
3542import Data.Ord
3643import qualified Data.Set as S
3744import Data.Text (Text)
3845import qualified Data.Text as T
39−import OpenAPI.Checker.Memo
40−import OpenAPI.Checker.References
41−import OpenAPI.Checker.Subtree
42−import OpenAPI.Checker.Validate.Schema.DNF
43−import OpenAPI.Checker.Validate.Schema.JsonFormula
44−import OpenAPI.Checker.Validate.Schema.Traced
45−import OpenAPI.Checker.Validate.Schema.TypedJson
4646import Text.Pandoc.Builder hiding (Format, Null)
4747
4848data PartitionData
@@ -143,10 +143,11 @@ partitionCondition = \case
143143 Just _ -> top
144144 Nothing ->
145145 singletonPart $
146− DByProperties $ LiteralDNF
147− ( M.keysSet $ M.filter (not . propRequired) props
148− , M.keysSet $ M.filter propRequired props
149− )
146+ DByProperties $
147+ LiteralDNF
148+ ( M.keysSet $ M.filter (not . propRequired) props
149+ , M.keysSet $ M.filter propRequired props
150+ )
150151 inProps <- forM (M.toList $ M.filter propRequired props) $ \(k, prop) -> do
151152 f <- partitionRefSchema $ propRefSchema prop
152153 pure $ fmap (\(Partitions m) -> Partitions $ M.mapKeysMonotonic (PInProperty k) m) f
@@ -156,10 +157,10 @@ partitionCondition = \case
156157runPartitionM :: Traced (Definitions Schema) -> PartitionM a -> a
157158runPartitionM defs = runIdentity . runMemo () . (`runReaderT` defs)
158159
159−partitionJsonFormulas
160− :: ProdCons (Traced (Definitions Schema))
161− -> ProdCons (JsonFormula t)
162− -> Lifted Partitions
160+partitionJsonFormulas ::
161+ ProdCons (Traced (Definitions Schema)) ->
162+ ProdCons (JsonFormula t) ->
163+ Lifted Partitions
163164partitionJsonFormulas defs pc = producer pcPart \/ consumer pcPart
164165 where
165166 pcPart = partitionFormula <$> defs <*> pc
@@ -211,11 +212,11 @@ runIntersectionM defs act = case runWriterT $ runReaderT act defs of
211212 Just (x, Any False) -> Same x
212213 Just (x, Any True) -> New x
213214
214−intersectSchema
215− :: PartitionLocation
216− -> PartitionChoice
217− -> Traced Schema
218− -> IntersectionM Schema
215+intersectSchema ::
216+ PartitionLocation ->
217+ PartitionChoice ->
218+ Traced Schema ->
219+ IntersectionM Schema
219220intersectSchema loc part sch = do
220221 allOf' <- forM (tracedAllOf sch) $ \rss ->
221222 -- Assuming i ranges over a nonempty set (checked in processSchema)
@@ -258,11 +259,11 @@ intersectSchema loc part sch = do
258259 pure $ sch' {_schemaEnum = Just enum'}
259260 CByProperties {} -> error "CByProperties not implemented"
260261
261−intersectRefSchema
262− :: PartitionLocation
263− -> PartitionChoice
264− -> Traced (Referenced Schema)
265− -> IntersectionM (Referenced Schema)
262+intersectRefSchema ::
263+ PartitionLocation ->
264+ PartitionChoice ->
265+ Traced (Referenced Schema) ->
266+ IntersectionM (Referenced Schema)
266267intersectRefSchema loc part rs = do
267268 defs <- R.ask
268269 Inline <$> intersectSchema loc part (dereference defs rs)
src/OpenAPI/Checker/Validate/Schema/Process.hs → src/Data/OpenApi/Compare/Validate/Schema/Process.hsrenamed+48−47
@@ -1,11 +1,11 @@
1−module OpenAPI.Checker.Validate.Schema.Process
2− ( schemaToFormula
1+module Data.OpenApi.Compare.Validate.Schema.Process
2+ ( schemaToFormula,
33 )
44where
55
66import Algebra.Lattice
7−import qualified Control.Monad.Reader as R
87import Control.Monad.Reader hiding (ask)
8+import qualified Control.Monad.Reader as R
99import Control.Monad.State
1010import Control.Monad.Writer
1111import qualified Data.Aeson as A
@@ -14,20 +14,20 @@ import qualified Data.HashMap.Strict.InsOrd as IOHM
1414import qualified Data.Map as M
1515import Data.Maybe
1616import Data.OpenApi hiding (get)
17+import Data.OpenApi.Compare.Behavior
18+import Data.OpenApi.Compare.Memo
19+import Data.OpenApi.Compare.Paths
20+import qualified Data.OpenApi.Compare.PathsPrefixTree as P
21+import Data.OpenApi.Compare.References
22+import Data.OpenApi.Compare.Subtree
23+import Data.OpenApi.Compare.Validate.Schema.DNF
24+import Data.OpenApi.Compare.Validate.Schema.Issues
25+import Data.OpenApi.Compare.Validate.Schema.JsonFormula
26+import Data.OpenApi.Compare.Validate.Schema.Partition
27+import Data.OpenApi.Compare.Validate.Schema.Traced
28+import Data.OpenApi.Compare.Validate.Schema.TypedJson
1729import Data.Ord
1830import qualified Data.Set as S
19−import OpenAPI.Checker.Behavior
20−import OpenAPI.Checker.Memo
21−import OpenAPI.Checker.Paths
22−import qualified OpenAPI.Checker.PathsPrefixTree as P
23−import OpenAPI.Checker.References
24−import OpenAPI.Checker.Subtree
25−import OpenAPI.Checker.Validate.Schema.DNF
26−import OpenAPI.Checker.Validate.Schema.Issues
27−import OpenAPI.Checker.Validate.Schema.JsonFormula
28−import OpenAPI.Checker.Validate.Schema.Partition
29−import OpenAPI.Checker.Validate.Schema.Traced
30−import OpenAPI.Checker.Validate.Schema.TypedJson
3131
3232-- | A fake writer monad that doesn't actually record anything and allows lazy recursion.
3333newtype Silent w a = Silent {runSilent :: a}
@@ -71,10 +71,10 @@ warnKnot =
7171 , tieKnot = \_ -> pure
7272 }
7373
74−processRefSchema
75− :: MonadProcess m
76− => Traced (Referenced Schema)
77− -> m (ForeachType JsonFormula)
74+processRefSchema ::
75+ MonadProcess m =>
76+ Traced (Referenced Schema) ->
77+ m (ForeachType JsonFormula)
7878processRefSchema x = do
7979 defs <- R.ask
8080 memoWithKnot warnKnot (processSchema $ dereference defs x) (ask x)
@@ -82,10 +82,10 @@ processRefSchema x = do
8282-- | Turn a schema into a tuple of 'JsonFormula's that describes the condition
8383-- for every possible type of a JSON value. The conditions are independent, and
8484-- are thus checked independently.
85−processSchema
86− :: MonadProcess m
87− => Traced Schema
88− -> m (ForeachType JsonFormula)
85+processSchema ::
86+ MonadProcess m =>
87+ Traced Schema ->
88+ m (ForeachType JsonFormula)
8989processSchema sch@(extract -> Schema {..}) = do
9090 let singletonFormula :: Condition t -> JsonFormula t
9191 singletonFormula = JsonFormula . LiteralDNF
@@ -331,26 +331,27 @@ processSchema sch@(extract -> Schema {..}) = do
331331 pure $
332332 nullableClause
333333 \/ meets
334− (allClauses
335− <> [ anyClause
336− , oneClause
337− , typeClause
338− , enumClause
339− , maximumClause
340− , minimumClause
341− , multipleOfClause
342− , formatClause
343− , maxLengthClause
344− , minLengthClause
345− , patternClause
346− , itemsClause
347− , maxItemsClause
348− , minItemsClause
349− , uniqueItemsClause
350− , propertiesClause
351− , maxPropertiesClause
352− , minPropertiesClause
353− ])
334+ ( allClauses
335+ <> [ anyClause
336+ , oneClause
337+ , typeClause
338+ , enumClause
339+ , maximumClause
340+ , minimumClause
341+ , multipleOfClause
342+ , formatClause
343+ , maxLengthClause
344+ , minLengthClause
345+ , patternClause
346+ , itemsClause
347+ , maxItemsClause
348+ , minItemsClause
349+ , uniqueItemsClause
350+ , propertiesClause
351+ , maxPropertiesClause
352+ , minPropertiesClause
353+ ]
354+ )
354355
355356{- TODO: ReadOnly/WriteOnly #68 -}
356357
@@ -369,8 +370,8 @@ checkOneOfDisjoint schs = do
369370runProcessM :: Traced (Definitions Schema) -> ProcessM a -> (a, P.PathsPrefixTree Behave AnIssue 'SchemaLevel)
370371runProcessM defs = runWriter . (`runReaderT` defs) . runMemo ()
371372
372−schemaToFormula
373− :: Traced (Definitions Schema)
374− -> Traced Schema
375− -> (ForeachType JsonFormula, P.PathsPrefixTree Behave AnIssue 'SchemaLevel)
373+schemaToFormula ::
374+ Traced (Definitions Schema) ->
375+ Traced Schema ->
376+ (ForeachType JsonFormula, P.PathsPrefixTree Behave AnIssue 'SchemaLevel)
376377schemaToFormula defs rs = runProcessM defs $ processSchema rs
src/OpenAPI/Checker/Validate/Schema/Traced.hs → src/Data/OpenApi/Compare/Validate/Schema/Traced.hsrenamed+14−14
@@ -1,18 +1,18 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Validate.Schema.Traced
4− ( Step (..)
5− , tracedAllOf
6− , tracedAnyOf
7− , tracedOneOf
8− , tracedItems
9− , tracedAdditionalProperties
10− , tracedDiscriminator
11− , tracedProperties
12− , tracedConjunct
13− , PartitionLocation (..)
14− , PartitionChoice (..)
15− , Partition
3+module Data.OpenApi.Compare.Validate.Schema.Traced
4+ ( Step (..),
5+ tracedAllOf,
6+ tracedAnyOf,
7+ tracedOneOf,
8+ tracedItems,
9+ tracedAdditionalProperties,
10+ tracedDiscriminator,
11+ tracedProperties,
12+ tracedConjunct,
13+ PartitionLocation (..),
14+ PartitionChoice (..),
15+ Partition,
1616 )
1717where
1818
@@ -21,9 +21,9 @@ import Data.Functor
2121import qualified Data.HashMap.Strict.InsOrd as IOHM
2222import qualified Data.List.NonEmpty as NE
2323import Data.OpenApi
24+import Data.OpenApi.Compare.Subtree
2425import qualified Data.Set as S
2526import Data.Text (Text)
26−import OpenAPI.Checker.Subtree
2727
2828data PartitionChoice
2929 = CByEnumValue (S.Set A.Value)
src/OpenAPI/Checker/Validate/Schema/TypedJson.hs → src/Data/OpenApi/Compare/Validate/Schema/TypedJson.hsrenamed+26−24
@@ -1,11 +1,11 @@
1−module OpenAPI.Checker.Validate.Schema.TypedJson
2− ( JsonType (..)
3− , describeJSONType
4− , TypedValue (..)
5− , untypeValue
6− , ForeachType (..)
7− , foldType
8− , forType_
1+module Data.OpenApi.Compare.Validate.Schema.TypedJson
2+ ( JsonType (..),
3+ describeJSONType,
4+ TypedValue (..),
5+ untypeValue,
6+ ForeachType (..),
7+ foldType,
8+ forType_,
99 )
1010where
1111
@@ -88,24 +88,26 @@ forType_ :: Applicative m => (forall x. Typeable x => JsonType -> (ForeachType f
8888forType_ k = getAp $ foldType (\ty proj -> Ap $ k ty proj)
8989
9090broadcastType :: (forall x. Typeable x => f x) -> ForeachType f
91−broadcastType k = ForeachType
92− { forNull = k
93− , forBoolean = k
94− , forNumber = k
95− , forString = k
96− , forArray = k
97− , forObject = k
98− }
91+broadcastType k =
92+ ForeachType
93+ { forNull = k
94+ , forBoolean = k
95+ , forNumber = k
96+ , forString = k
97+ , forArray = k
98+ , forObject = k
99+ }
99100
100101zipType :: (forall x. Typeable x => f x -> g x -> h x) -> ForeachType f -> ForeachType g -> ForeachType h
101−zipType k f1 f2 = ForeachType
102− { forNull = k (forNull f1) (forNull f2)
103− , forBoolean = k (forBoolean f1) (forBoolean f2)
104− , forNumber = k (forNumber f1) (forNumber f2)
105− , forString = k (forString f1) (forString f2)
106− , forArray = k (forArray f1) (forArray f2)
107− , forObject = k (forObject f1) (forObject f2)
108− }
102+zipType k f1 f2 =
103+ ForeachType
104+ { forNull = k (forNull f1) (forNull f2)
105+ , forBoolean = k (forBoolean f1) (forBoolean f2)
106+ , forNumber = k (forNumber f1) (forNumber f2)
107+ , forString = k (forString f1) (forString f2)
108+ , forArray = k (forArray f1) (forArray f2)
109+ , forObject = k (forObject f1) (forObject f2)
110+ }
109111
110112instance (forall x. Lattice (f x)) => Lattice (ForeachType f) where
111113 (\/) = zipType (\/)
src/OpenAPI/Checker/Validate/SecurityRequirement.hs → src/Data/OpenApi/Compare/Validate/SecurityRequirement.hsrenamed+14−13
@@ -1,8 +1,8 @@
11{-# LANGUAGE PartialTypeSignatures #-}
22{-# OPTIONS_GHC -Wno-orphans #-}
33
4−module OpenAPI.Checker.Validate.SecurityRequirement
5− ( Issue (..)
4+module Data.OpenApi.Compare.Validate.SecurityRequirement
5+ ( Issue (..),
66 )
77where
88
@@ -17,14 +17,14 @@ import Data.HList
1717import qualified Data.HashMap.Strict.InsOrd as IOHM
1818import qualified Data.List.NonEmpty as NE
1919import Data.OpenApi
20+import Data.OpenApi.Compare.Behavior
21+import Data.OpenApi.Compare.Subtree
22+import Data.OpenApi.Compare.Validate.OAuth2Flows
23+import Data.OpenApi.Compare.Validate.SecurityScheme ()
2024import Data.Set (Set)
2125import qualified Data.Set as S
2226import Data.Text (Text)
2327import Data.Traversable
24−import OpenAPI.Checker.Behavior
25−import OpenAPI.Checker.Subtree
26−import OpenAPI.Checker.Validate.OAuth2Flows
27−import OpenAPI.Checker.Validate.SecurityScheme ()
2828
2929instance Subtree SecurityRequirement where
3030 type SubtreeLevel SecurityRequirement = 'SecurityRequirementLevel
@@ -41,18 +41,19 @@ instance Subtree SecurityRequirement where
4141 for (IOHM.toList $ getSecurityRequirement sec) $ \(key, scopes) ->
4242 (,scopes) <$> lookupScheme key defs
4343 structuralMaybeWith
44− (\pc' -> do
45− let ProdCons pScopes cScopes = fmap snd <$> pc'
46− unless (pScopes == cScopes) structuralIssue
47− structuralList env $ fmap fst <$> pc'
48− pure ())
44+ ( \pc' -> do
45+ let ProdCons pScopes cScopes = fmap snd <$> pc'
46+ unless (pScopes == cScopes) structuralIssue
47+ structuralList env $ fmap fst <$> pc'
48+ pure ()
49+ )
4950 normalized
5051 pure ()
5152 checkSemanticCompatibility env bhv' pc = do
5253 let schemes = getH @(ProdCons (Traced (Definitions SecurityScheme))) env
5354 ( ProdCons pErrs cErrs
54− , (ProdCons pSchemes cSchemes)
55− :: ProdCons [(Behavior 'SecuritySchemeLevel, Traced SecurityScheme, [Text])]
55+ , (ProdCons pSchemes cSchemes) ::
56+ ProdCons [(Behavior 'SecuritySchemeLevel, Traced SecurityScheme, [Text])]
5657 ) =
5758 NE.unzip $
5859 partitionEithers <$> do
src/OpenAPI/Checker/Validate/SecurityScheme.hs → src/Data/OpenApi/Compare/Validate/SecurityScheme.hsrenamed+5−5
@@ -1,16 +1,16 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Validate.SecurityScheme
3+module Data.OpenApi.Compare.Validate.SecurityScheme
44 (
55 )
66where
77
88import Control.Monad
99import Data.OpenApi
10−import OpenAPI.Checker.Behavior
11−import OpenAPI.Checker.Orphans ()
12−import OpenAPI.Checker.Subtree
13−import OpenAPI.Checker.Validate.OAuth2Flows
10+import Data.OpenApi.Compare.Behavior
11+import Data.OpenApi.Compare.Orphans ()
12+import Data.OpenApi.Compare.Subtree
13+import Data.OpenApi.Compare.Validate.OAuth2Flows
1414
1515instance Subtree SecurityScheme where
1616 type CheckEnv SecurityScheme = '[]
src/OpenAPI/Checker/Validate/Server.hs → src/Data/OpenApi/Compare/Validate/Server.hsrenamed+17−16
@@ -1,7 +1,7 @@
11{-# OPTIONS_GHC -Wno-orphans #-}
22
3−module OpenAPI.Checker.Validate.Server
4− ( Issue (..)
3+module Data.OpenApi.Compare.Validate.Server
4+ ( Issue (..),
55 )
66where
77
@@ -19,21 +19,21 @@ import qualified Data.HashSet.InsOrd as IOHM
1919import qualified Data.HashSet.InsOrd as IOHS
2020import Data.Maybe
2121import Data.OpenApi
22+import Data.OpenApi.Compare.Behavior
23+import Data.OpenApi.Compare.Common
24+import Data.OpenApi.Compare.Paths
25+import Data.OpenApi.Compare.Subtree
26+import Data.OpenApi.Compare.Validate.MediaTypeObject
2227import qualified Data.Set as S
2328import Data.Text (Text)
2429import qualified Data.Text as T
2530import Data.Traversable
26−import OpenAPI.Checker.Behavior
27−import OpenAPI.Checker.Common
28−import OpenAPI.Checker.Paths
29−import OpenAPI.Checker.Subtree
30−import OpenAPI.Checker.Validate.MediaTypeObject
3131import Text.Pandoc.Builder
3232import Prelude as P
3333
34−tracedParsedServerUrlParts
35− :: Server
36− -> Either (Issue 'ServerLevel) ProcessedServer
34+tracedParsedServerUrlParts ::
35+ Server ->
36+ Either (Issue 'ServerLevel) ProcessedServer
3737tracedParsedServerUrlParts s =
3838 let parsedUrl = parseServerUrl $ _serverUrl s
3939 lookupVar var = case IOHM.lookup var (_serverVariables s) of
@@ -63,11 +63,12 @@ instance Subtree [Server] where
6363 pcServer
6464 <&> partitionEithers
6565 . fmap
66− (\(Traced t s) ->
67− let bhv = beh >>> step (InServer $ _serverUrl s)
68− in case tracedParsedServerUrlParts s of
69− Left e -> Left $ issueAt bhv e
70− Right u -> Right (bhv, Traced (t >>> step (ServerStep $ _serverUrl s)) u))
66+ ( \(Traced t s) ->
67+ let bhv = beh >>> step (InServer $ _serverUrl s)
68+ in case tracedParsedServerUrlParts s of
69+ Left e -> Left $ issueAt bhv e
70+ Right u -> Right (bhv, Traced (t >>> step (ServerStep $ _serverUrl s)) u)
71+ )
7172 . sequence
7273 sequenceA_ pErrs
7374 sequenceA_ cErrs
@@ -101,7 +102,7 @@ data ServerUrlPart var
101102 deriving stock (Eq, Show, Functor, Foldable, Traversable)
102103
103104-- | This is super rough. Things like @{a|b}c@ will not match @ac@.
104−-- FIXME: https://github.com/typeable/openapi-diff/issues/46
105+-- FIXME: #46
105106--
106107-- NOTE: syntax is defined vaguely in the spec.
107108parseServerUrl :: Text -> [ServerUrlPart Text]
src/Data/OpenApi/Compare/Validate/Sums.hsnew file+25
@@ -0,0 +1,25 @@
1+module Data.OpenApi.Compare.Validate.Sums
2+ ( checkSums,
3+ )
4+where
5+
6+import Data.Foldable
7+import Data.Map.Strict (Map)
8+import qualified Data.Map.Strict as M
9+import Data.OpenApi.Compare.Behavior
10+import Data.OpenApi.Compare.Paths
11+import Data.OpenApi.Compare.Subtree
12+
13+checkSums ::
14+ (Ord k, Issuable l) =>
15+ Paths q r l ->
16+ (k -> Issue l) ->
17+ (k -> ProdCons t -> CompatFormula' q AnIssue r ()) ->
18+ ProdCons (Map k t) ->
19+ CompatFormula' q AnIssue r ()
20+checkSums xs noElt check (ProdCons p c) = for_ (M.toList p) $ \(key, prodElt) ->
21+ case M.lookup key c of
22+ Nothing -> issueAt xs $ noElt key
23+ Just consElt ->
24+ let sumElts = ProdCons prodElt consElt
25+ in check key sumElts
src/Data/OpenUnion/Extra.hs+5−5
@@ -1,7 +1,7 @@
11module Data.OpenUnion.Extra
2− ( (@@>)
3− , TryLiftUnion (..)
4− , pattern SingletonUnion
2+ ( (@@>),
3+ TryLiftUnion (..),
4+ pattern SingletonUnion,
55 )
66where
77
@@ -18,8 +18,8 @@ instance TryLiftUnion '[] where
1818 tryLiftUnion _ = empty
1919
2020instance
21− (Typeable y, SubList ys (y : ys), TryLiftUnion ys)
22− => TryLiftUnion (y ': ys)
21+ (Typeable y, SubList ys (y : ys), TryLiftUnion ys) =>
22+ TryLiftUnion (y ': ys)
2323 where
2424 tryLiftUnion (x :: x) = case eqT @x @y of
2525 Nothing -> reUnion <$> tryLiftUnion @ys x
src/OpenAPI/Checker/Memo.hsdeleted−126
@@ -1,126 +0,0 @@
1−{-# LANGUAGE AllowAmbiguousTypes #-}
2−-- | Utilities for effectfully memoizing other, more effectful functions.
3−module OpenAPI.Checker.Memo
4− ( MonadMemo
5− , MemoState
6− , runMemo
7− , modifyMemoNonce
8− , KnotTier(..)
9− , unknot
10− , memoWithKnot
11− , memoTaggedWithKnot
12− ) where
13−
14−import Control.Monad.State
15−import Data.Dynamic
16−import qualified Data.Map as M
17−import Data.Tagged
18−import Data.Void
19−import qualified Data.TypeRepMap as T
20−import Type.Reflection
21−
22−data Progress a = Finished a | Started | TyingKnot Dynamic
23−
24−data MemoMap a where
25− MemoMap :: !(M.Map k (Progress v)) -> MemoMap (k, v)
26−
27−data MemoState s = MemoState s (T.TypeRepMap MemoMap)
28−
29−-- | An effectful memoization monad.
30−type MonadMemo s m = MonadState (MemoState s) m
31−
32−memoStateLookup
33− :: forall k v s. (Typeable k, Typeable v, Ord k)
34− => k -> MemoState s -> Maybe (Progress v)
35−memoStateLookup k (MemoState _ t) = case T.lookup @(k, v) t of
36− Just (MemoMap m) -> M.lookup k m
37− Nothing -> Nothing
38−
39−memoStateInsert
40− :: forall k v s. (Typeable k, Typeable v, Ord k)
41− => k -> Progress v -> MemoState s -> MemoState s
42−memoStateInsert k x (MemoState s t) = MemoState s $ T.insert (MemoMap m'') t
43− where
44− m'' = M.insert k x m'
45− m' = case T.lookup @(k, v) t of
46− Just (MemoMap m) -> m
47− Nothing -> M.empty
48−
49−modifyMemoNonce :: MonadMemo s m => (s -> s) -> m s
50−modifyMemoNonce f = do
51− MemoState s t <- get
52− put $ MemoState (f s) t
53− pure s
54−
55−-- | Run a memoized computation.
56−runMemo :: Monad m => s -> StateT (MemoState s) m a -> m a
57−runMemo s = (`evalStateT` MemoState s T.empty)
58−
59−-- | A description of how to effectfully tie knots in type @v@, using the @m@
60−-- monad, and by sharing some @d@ data among the recursive instances.
61−data KnotTier v d m = KnotTier
62− { onKnotFound :: m d -- ^ Create some data that will be connected to this knot
63− , onKnotUsed :: d -> m v -- ^ This is what the knot will look like as a value
64− -- to the inner computations
65− , tieKnot :: d -> v -> m v -- ^ Once we're done and we're outside, tie the
66− -- knot using the datum
67− }
68−
69−unknot :: KnotTier v Void m
70−unknot = KnotTier
71− { onKnotFound = error "Recursion detected"
72− , onKnotUsed = absurd
73− , tieKnot = absurd
74− }
75−
76−-- | Run a potentially recursive computation. The provided key will be used to
77−-- refer to the result of this computation. If during the computation, another
78−-- attempt to run the computation with the same key is made, we run a
79−-- tying-the-knot procedure.
80−--
81−-- If another attempt to run the computation with the same key is made
82−-- *after we're done*, we will return the memoized value.
83−memoWithKnot
84− :: forall k v d m s.
85− (Typeable k, Typeable v, Typeable d, Ord k, MonadMemo s m)
86− => KnotTier v d m
87− -> m v -- ^ the computation to memoize
88− -> k -- ^ key for memoization
89− -> m v
90−memoWithKnot tier f k = memoStateLookup @k @v k <$> get >>= \case
91− Just (Finished v) -> pure v
92− Just Started -> do
93− d <- onKnotFound tier
94− modify $ memoStateInsert @k @v k (TyingKnot $ toDyn d)
95− onKnotUsed tier d
96− Just (TyingKnot dyn) -> case fromDynamic dyn of
97− Just d -> onKnotUsed tier d
98− Nothing -> error $ "Type mismatch when examining the knot of "
99− <> show (typeRep @(k -> v)) <> ": expected " <> show (typeRep @d)
100− <> ", got " <> show (dynTypeRep dyn)
101− Nothing -> do
102− modify $ memoStateInsert @k @v k Started
103− v <- f
104− v' <- memoStateLookup @k @v k <$> get >>= \case
105− Just Started -> pure v
106− Just (TyingKnot dyn) -> case fromDynamic dyn of
107− Just d -> tieKnot tier d v
108− Nothing -> error $ "Type mismatch when tying the knot of "
109− <> show (typeRep @(k -> v)) <> ": expected " <> show (typeRep @d)
110− <> ", got " <> show (dynTypeRep dyn)
111− Just (Finished _) -> error $ "Unexpected Finished when memoizing "
112− <> show (typeRep @(k -> v))
113− Nothing -> pure v
114− -- Normally this would be an error, but the underlying monad can refuse
115− -- to remember memoization state
116− modify $ memoStateInsert @k @v k (Finished v')
117− pure v'
118−
119−-- | Disambiguate memoized computations with an arbitrary tag.
120−memoTaggedWithKnot
121− :: forall t k v d m s.
122− ( Typeable t, Typeable k, Typeable v, Typeable d
123− , Ord k, MonadMemo s m )
124− => KnotTier v d m -> m v -> k -> m v
125−memoTaggedWithKnot tier f k = withTypeable (typeRepKind $ typeRep @t) $
126− memoWithKnot tier f (Tagged @t k)
src/OpenAPI/Checker/Validate/Products.hsdeleted−49
@@ -1,49 +0,0 @@
1−{- | Checks product-like entities. The key is some identificator for the product
2−element. Each element may be required or optional.
3−
4−One example of product is request parameters. There are optional and required
5−parameters. The client and server have possibly different set of
6−parameters. What we must check is if server requires some request parameter,
7−then this parameter must be presented by client and their schemas must match.
8−
9−So when we checking products we are checking from the server's (consumer)
10−perspective, ensuring that all parameters are provided by the client (producer)
11−and their schemas match.
12−
13−This module abstracts this logic for arbitrary elements -}
14−
15−module OpenAPI.Checker.Validate.Products
16− ( checkProducts
17− , ProductLike(..)
18− ) where
19−
20−import Data.Foldable
21−import Data.Map.Strict (Map)
22−import qualified Data.Map.Strict as M
23−import OpenAPI.Checker.Behavior
24−import OpenAPI.Checker.Paths
25−import OpenAPI.Checker.Subtree
26−
27−-- | Some entity which is product-like
28−data ProductLike a = ProductLike
29− { productValue :: a
30− , required :: Bool
31− }
32−
33−checkProducts
34− :: (Ord k, Issuable l)
35− => Paths q r l
36− -> (k -> Issue l)
37− -- ^ No required element found
38− -> (k -> ProdCons t -> CompatFormula' q AnIssue r ())
39− -> ProdCons (Map k (ProductLike t))
40− -> CompatFormula' q AnIssue r ()
41−checkProducts xs noElt check (ProdCons p c) = for_ (M.toList c) $ \(key, consElt) ->
42− case M.lookup key p of
43− Nothing -> case required consElt of
44− True -> issueAt xs $ noElt key
45− False -> pure ()
46− Just prodElt -> do
47− let
48− elts = ProdCons prodElt consElt
49− check key (productValue <$> elts)
src/OpenAPI/Checker/Validate/Sums.hsdeleted−24
@@ -1,24 +0,0 @@
1−module OpenAPI.Checker.Validate.Sums
2− ( checkSums
3− ) where
4−
5−import Data.Foldable
6−import Data.Map.Strict (Map)
7−import qualified Data.Map.Strict as M
8−import OpenAPI.Checker.Behavior
9−import OpenAPI.Checker.Subtree
10−import OpenAPI.Checker.Paths
11−
12−checkSums
13− :: (Ord k, Issuable l)
14− => Paths q r l
15− -> (k -> Issue l)
16− -> (k -> ProdCons t -> CompatFormula' q AnIssue r ())
17− -> ProdCons (Map k t)
18− -> CompatFormula' q AnIssue r ()
19−checkSums xs noElt check (ProdCons p c) = for_ (M.toList p) $ \(key, prodElt) ->
20− case M.lookup key c of
21− Nothing -> issueAt xs $ noElt key
22− Just consElt ->
23− let sumElts = ProdCons prodElt consElt
24− in check key sumElts
stack.yaml+7−4
@@ -1,8 +1,11 @@
1−resolver: lts-18.2
1+resolver: lts-18.16
2+
3+packages:
4+ - .
25
36extra-deps:
47 - open-union-0.4.0.0
58 - type-fun-0.1.3
6−
7−apply-ghc-options: everything
8−rebuild-ghc-options: true
9+ - github-0.26
10+ - base16-bytestring-0.1.1.7
11+ - http-link-header-1.0.3.1
stack.yaml.lock+25−4
@@ -18,9 +18,30 @@ packages:
1818 sha256: b977d42525f0b4223959918d18e7905085283e937493c395077b608cd19b42c1
1919 original:
2020 hackage: type-fun-0.1.3
21+- completed:
22+ hackage: github-0.26@sha256:a9d4046325c3eb28cdc7bef2c3f5bb213328caeae0b7dce6f51de655f0bffaa1,7162
23+ pantry-tree:
24+ size: 7511
25+ sha256: b71aab2984b268030c9e2617043575681134c1fe60dffbd5596e659c0a3e9aec
26+ original:
27+ hackage: github-0.26
28+- completed:
29+ hackage: base16-bytestring-0.1.1.7@sha256:0021256a9628971c08da95cb8f4d0d72192f3bb8a7b30b55c080562d17c43dd3,2231
30+ pantry-tree:
31+ size: 454
32+ sha256: 2dc99cd67293b09204098202d0bdcdcf89d2b8c69ce6c8f4e31deadbdab2a7f9
33+ original:
34+ hackage: base16-bytestring-0.1.1.7
35+- completed:
36+ hackage: http-link-header-1.0.3.1@sha256:9917c26635a46eff5d36c5344a4ae42fad2b5de79c0e300363bddd5eec66facd,1937
37+ pantry-tree:
38+ size: 990
39+ sha256: 155feaf856daf5b55491048c8c4726ba765a8fe79263ebdbfc82f6c385a15715
40+ original:
41+ hackage: http-link-header-1.0.3.1
2142snapshots:
2243- completed:
23− size: 585392
24− url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/2.yaml
25− sha256: 7abb45c0cc5eb349448b66d8753655542d45d387ad26970419282eab3d860724
26− original: lts-18.2
44+ size: 586286
45+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/16.yaml
46+ sha256: cdead65fca0323144b346c94286186f4969bf85594d649c49c7557295675d8a5
47+ original: lts-18.16
test/README.md+1−1
@@ -30,7 +30,7 @@ This test would read the files `a.yaml` and `b.yaml` using the `Yaml.decodeFileT
3030
3131## Supported feature tests
3232
33−If a test starts with either `x ` or `v `, the test is assumed to test the support of some OpenApi functionality. If the test begins `x ` it means that the feature is expected to be not supported, and the result will only be checked to have an "unsupported" flag set. If it starts with `v ` the test is expected to be supported and the result is compared to some file on disk.
33+If a test starts with either `x ` or `v `, the test is assumed to test the support of some OpenAPI functionality. If the test begins `x ` it means that the feature is expected to be not supported, and the result will only be checked to have an "unsupported" flag set. If it starts with `v ` the test is expected to be supported and the result is compared to some file on disk.
3434
3535The result will be reflected in the generated compatibility matrix.
3636
test/Spec/Golden/Extra.hs+46−42
@@ -1,8 +1,8 @@
11module Spec.Golden.Extra
2− ( getGoldenInputs
3− , getGoldenInputsUniform
4− , goldenInputsTree
5− , goldenInputsTreeUniform
2+ ( getGoldenInputs,
3+ getGoldenInputsUniform,
4+ goldenInputsTree,
5+ goldenInputsTreeUniform,
66 )
77where
88
@@ -19,12 +19,12 @@ data TestInput t
1919 | TestInputLeaf TestName t FilePath
2020 deriving stock (Functor)
2121
22−getGoldenInputs
23− :: (Each s t (FilePath, FilePath -> IO a) a)
24− => TestName
25− -> FilePath
26− -> s
27− -> IO (TestInput t)
22+getGoldenInputs ::
23+ (Each s t (FilePath, FilePath -> IO a) a) =>
24+ TestName ->
25+ FilePath ->
26+ s ->
27+ IO (TestInput t)
2828getGoldenInputs name filepath inp = do
2929 dirs' <- listDirectory filepath >>= filterM (doesDirectoryExist . (filepath </>))
3030 case dirs' of
@@ -39,32 +39,34 @@ getGoldenInputs name filepath inp = do
3939 TestInputNode name
4040 <$> forM dirs (\dir -> getGoldenInputs dir (filepath </> dir) inp)
4141
42−getGoldenInputsUniform
43− :: (Each t h (FilePath, FilePath -> IO a) a)
44− => (Each s t FilePath (FilePath, FilePath -> IO a))
45− => TestName
46− -> (FilePath -> IO a)
47− -> FilePath
48− -> s
49− -> IO (TestInput h)
42+getGoldenInputsUniform ::
43+ (Each t h (FilePath, FilePath -> IO a) a) =>
44+ (Each s t FilePath (FilePath, FilePath -> IO a)) =>
45+ TestName ->
46+ (FilePath -> IO a) ->
47+ FilePath ->
48+ s ->
49+ IO (TestInput h)
5050getGoldenInputsUniform name f filepath inp = getGoldenInputs name filepath $ inp & each %~ (,f)
5151
52−goldenInputsTree
53− :: (Each s t (FilePath, FilePath -> IO a) a)
54− => TestName
55− -> FilePath -- ^ Root path
56− -> FilePath -- ^ Name of golden file
57− -> s
58− -> (t -> IO BSL.ByteString)
59− -> IO TestTree
52+goldenInputsTree ::
53+ (Each s t (FilePath, FilePath -> IO a) a) =>
54+ TestName ->
55+ -- | Root path
56+ FilePath ->
57+ -- | Name of golden file
58+ FilePath ->
59+ s ->
60+ (t -> IO BSL.ByteString) ->
61+ IO TestTree
6062goldenInputsTree name filepath golden inp f = do
6163 runTestInputTree golden f <$> getGoldenInputs name filepath inp
6264
63−runTestInputTree
64− :: FilePath
65− -> (t -> IO BSL.ByteString)
66− -> TestInput t
67− -> TestTree
65+runTestInputTree ::
66+ FilePath ->
67+ (t -> IO BSL.ByteString) ->
68+ TestInput t ->
69+ TestTree
6870runTestInputTree golden f (TestInputNode name rest) =
6971 testGroup name (runTestInputTree golden f <$> rest)
7072runTestInputTree golden f (TestInputLeaf name t path) =
@@ -74,16 +76,18 @@ runTestInputTree golden f (TestInputLeaf name t path) =
7476 (path </> golden)
7577 (f t)
7678
77−goldenInputsTreeUniform
78− :: ( Each t h (FilePath, FilePath -> IO a) a
79− , Each s t FilePath (FilePath, FilePath -> IO a)
80− )
81− => String
82− -> FilePath -- ^ Root path
83− -> FilePath -- ^ Name of golden file
84− -> s
85− -> (FilePath -> IO a)
86− -> (h -> IO BSL.ByteString)
87− -> IO TestTree
79+goldenInputsTreeUniform ::
80+ ( Each t h (FilePath, FilePath -> IO a) a
81+ , Each s t FilePath (FilePath, FilePath -> IO a)
82+ ) =>
83+ String ->
84+ -- | Root path
85+ FilePath ->
86+ -- | Name of golden file
87+ FilePath ->
88+ s ->
89+ (FilePath -> IO a) ->
90+ (h -> IO BSL.ByteString) ->
91+ IO TestTree
8892goldenInputsTreeUniform name filepath golden inp h =
8993 goldenInputsTree name filepath golden (inp & each %~ (,h))
test/Spec/Golden/TraceTree.hs+9−6
@@ -1,5 +1,5 @@
11module Spec.Golden.TraceTree
2− ( tests
2+ ( tests,
33 )
44where
55
@@ -7,13 +7,15 @@ import Control.Category
77import Control.Exception
88import qualified Data.ByteString.Lazy as BSL
99import Data.Default
10+import Data.OpenApi.Compare.Run
11+import Data.OpenApi.Compare.Validate.OpenApi ()
1012import Data.Text (Text)
1113import qualified Data.Text.Encoding as T
1214import qualified Data.Yaml as Yaml
13−import OpenAPI.Checker.Run
14−import OpenAPI.Checker.Validate.OpenApi ()
15+import Paths_compaREST
1516import Spec.Golden.Extra
1617import Test.Tasty (TestTree, testGroup)
18+import Text.Pandoc.Builder
1719import Text.Pandoc.Class
1820import Text.Pandoc.Options
1921import Text.Pandoc.Writers
@@ -21,10 +23,11 @@ import Prelude hiding (id, (.))
2123
2224tests :: IO TestTree
2325tests = do
26+ dir <- getDataDir
2427 traceTreeTests <-
2528 goldenInputsTreeUniform
2629 "TraceTree"
27− "test/golden/common"
30+ dir
2831 "trace-tree.yaml"
2932 ("a.yaml", "b.yaml")
3033 Yaml.decodeFileThrow
@@ -33,11 +36,11 @@ tests = do
3336 reportTests <-
3437 goldenInputsTreeUniform
3538 "Report"
36− "test/golden/common"
39+ dir
3740 "report.md"
3841 ("a.yaml", "b.yaml")
3942 Yaml.decodeFileThrow
40− (runPandoc . writeMarkdown def {writerExtensions = githubMarkdownExtensions} . fst . runReport def)
43+ (runPandoc . writeMarkdown def {writerExtensions = githubMarkdownExtensions} . doc . fst . runReport def)
4144
4245 return $ testGroup "Golden tests" [traceTreeTests, reportTests]
4346
test/golden/common/bad-oneof-unchanged/report.md+6−6
@@ -1,19 +1,19 @@
11# Summary
22
3−| ⚠️ Breaking changes | 🙆 Non-breaking changes | [🤷 Unsupported feature changes](#unsupported-changes) |
4−|---------------------|------------------------|-------------------------------------------------------|
5−| 0 | 0 | 2 |
3+| ❌ Breaking changes | ⚠️ Non-breaking changes | [❓ Unsupported feature changes](#unsupported-changes) |
4+|---------------------|-------------------------|--------------------------------------------------------|
5+| 0 | 0 | 2 |
66
7−# <span id="unsupported-changes"></span>🤷 Unsupported feature changes
7+# <span id="unsupported-changes"></span>❓ Unsupported feature changes
88
99## **POST** /test
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313Could not deduce that `oneOf` cases don't overlap. Treating the `oneOf`
1414as an `anyOf`. Reported errors might not be accurate.
1515
16−### 📱⬅️ JSON Response – 200
16+### ⬅️☁️ JSON Response – 200
1717
1818Could not deduce that `oneOf` cases don't overlap. Treating the `oneOf`
1919as an `anyOf`. Reported errors might not be accurate.
test/golden/common/enum-added/report.md+7−7
@@ -1,14 +1,14 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 1 | 1 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 1 | 1 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱⬅️ JSON Response – 200
11+### ⬅️☁️ JSON Response – 200
1212
1313#### `$(String)`
1414
@@ -18,11 +18,11 @@ The following enum value was added:
1818"C"
1919```
2020
21−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
21+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
2222
2323## **POST** /test
2424
25−### 📱➡️ JSON Request
25+### ➡️☁️ JSON Request
2626
2727#### `$(String)`
2828
test/golden/common/enum-anyof/report.md+3−3
@@ -1,5 +1,5 @@
11# Summary
22
3−| ⚠️ Breaking changes | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|---------------------|------------------------|-------------------------------|
5−| 0 | 0 | 0 |
3+| ❌ Breaking changes | ⚠️ Non-breaking changes |
4+|---------------------|-------------------------|
5+| 0 | 0 |
test/golden/common/id/report.md+3−3
@@ -1,5 +1,5 @@
11# Summary
22
3−| ⚠️ Breaking changes | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|---------------------|------------------------|-------------------------------|
5−| 0 | 0 | 0 |
3+| ❌ Breaking changes | ⚠️ Non-breaking changes |
4+|---------------------|-------------------------|
5+| 0 | 0 |
test/golden/common/json/recursive/report.md+3−3
@@ -1,5 +1,5 @@
11# Summary
22
3−| ⚠️ Breaking changes | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|---------------------|------------------------|-------------------------------|
5−| 0 | 0 | 0 |
3+| ❌ Breaking changes | ⚠️ Non-breaking changes |
4+|---------------------|-------------------------|
5+| 0 | 0 |
test/golden/common/maximum-lowered/report.md+7−7
@@ -1,24 +1,24 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 1 | 1 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 1 | 1 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313#### `$(Number)`
1414
1515Upper bound changed from 3.0 inclusive to 2.0 inclusive.
1616
17−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
17+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
1818
1919## **POST** /test
2020
21−### 📱⬅️ JSON Response – 200
21+### ⬅️☁️ JSON Response – 200
2222
2323#### `$(Number)`
2424
test/golden/common/pathItem/operation/parameters/allowEmptyValue/reset/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|------------------------------------------|------------------------|-------------------------------|
5−| 1 | 0 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | ⚠️ Non-breaking changes |
4+|------------------------------------------|-------------------------|
5+| 1 | 0 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/parameters/allowEmptyValue/set/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| ⚠️ Breaking changes | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|---------------------|-------------------------------------------------|-------------------------------|
5−| 0 | 1 | 0 |
3+| ❌ Breaking changes | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|---------------------|--------------------------------------------------|
5+| 0 | 1 |
66
7−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
7+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/parameters/change/report.md+5−5
@@ -1,10 +1,10 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 1 | 1 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 1 | 1 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
@@ -16,7 +16,7 @@
1616
1717The type has been removed.
1818
19−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
19+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
2020
2121## **POST** /test
2222
test/golden/common/pathItem/operation/parameters/required/false/add/report.md+3−3
@@ -1,5 +1,5 @@
11# Summary
22
3−| ⚠️ Breaking changes | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|---------------------|------------------------|-------------------------------|
5−| 0 | 0 | 0 |
3+| ❌ Breaking changes | ⚠️ Non-breaking changes |
4+|---------------------|-------------------------|
5+| 0 | 0 |
test/golden/common/pathItem/operation/parameters/required/false/del/report.md+3−3
@@ -1,5 +1,5 @@
11# Summary
22
3−| ⚠️ Breaking changes | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|---------------------|------------------------|-------------------------------|
5−| 0 | 0 | 0 |
3+| ❌ Breaking changes | ⚠️ Non-breaking changes |
4+|---------------------|-------------------------|
5+| 0 | 0 |
test/golden/common/pathItem/operation/parameters/required/reset/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| ⚠️ Breaking changes | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|---------------------|-------------------------------------------------|-------------------------------|
5−| 0 | 1 | 0 |
3+| ❌ Breaking changes | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|---------------------|--------------------------------------------------|
5+| 0 | 1 |
66
7−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
7+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/parameters/required/set/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|------------------------------------------|------------------------|-------------------------------|
5−| 1 | 0 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | ⚠️ Non-breaking changes |
4+|------------------------------------------|-------------------------|
5+| 1 | 0 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/parameters/required/true/add/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|------------------------------------------|------------------------|-------------------------------|
5−| 1 | 0 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | ⚠️ Non-breaking changes |
4+|------------------------------------------|-------------------------|
5+| 1 | 0 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/parameters/required/true/del/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| ⚠️ Breaking changes | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|---------------------|-------------------------------------------------|-------------------------------|
5−| 0 | 1 | 0 |
3+| ❌ Breaking changes | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|---------------------|--------------------------------------------------|
5+| 0 | 1 |
66
7−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
7+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/add/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| ⚠️ Breaking changes | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|---------------------|-------------------------------------------------|-------------------------------|
5−| 0 | 1 | 0 |
3+| ❌ Breaking changes | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|---------------------|--------------------------------------------------|
5+| 0 | 1 |
66
7−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
7+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/change/report.md+7−7
@@ -1,24 +1,24 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 1 | 1 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 1 | 1 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313#### `$(String)`
1414
1515The type has been removed.
1616
17−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
17+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
1818
1919## **POST** /test
2020
21−### 📱➡️ JSON Request
21+### ➡️☁️ JSON Request
2222
2323#### `$(Number)`
2424
test/golden/common/pathItem/operation/requestBody/mediaTypeObject/del/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|------------------------------------------|------------------------|-------------------------------|
5−| 1 | 0 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | ⚠️ Non-breaking changes |
4+|------------------------------------------|-------------------------|
5+| 1 | 0 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/requestBody/required/reset/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| ⚠️ Breaking changes | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|---------------------|-------------------------------------------------|-------------------------------|
5−| 0 | 1 | 0 |
3+| ❌ Breaking changes | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|---------------------|--------------------------------------------------|
5+| 0 | 1 |
66
7−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
7+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/requestBody/required/set/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|------------------------------------------|------------------------|-------------------------------|
5−| 1 | 0 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | ⚠️ Non-breaking changes |
4+|------------------------------------------|-------------------------|
5+| 1 | 0 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/responses/add/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|------------------------------------------|------------------------|-------------------------------|
5−| 1 | 0 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | ⚠️ Non-breaking changes |
4+|------------------------------------------|-------------------------|
5+| 1 | 0 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/responses/change/headers/mandatory/add/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| ⚠️ Breaking changes | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|---------------------|-------------------------------------------------|-------------------------------|
5−| 0 | 1 | 0 |
3+| ❌ Breaking changes | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|---------------------|--------------------------------------------------|
5+| 0 | 1 |
66
7−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
7+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/responses/change/headers/mandatory/del/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|------------------------------------------|------------------------|-------------------------------|
5−| 1 | 0 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | ⚠️ Non-breaking changes |
4+|------------------------------------------|-------------------------|
5+| 1 | 0 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/responses/change/headers/optional/add/report.md+3−3
@@ -1,5 +1,5 @@
11# Summary
22
3−| ⚠️ Breaking changes | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|---------------------|------------------------|-------------------------------|
5−| 0 | 0 | 0 |
3+| ❌ Breaking changes | ⚠️ Non-breaking changes |
4+|---------------------|-------------------------|
5+| 0 | 0 |
test/golden/common/pathItem/operation/responses/change/headers/optional/del/report.md+3−3
@@ -1,5 +1,5 @@
11# Summary
22
3−| ⚠️ Breaking changes | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|---------------------|------------------------|-------------------------------|
5−| 0 | 0 | 0 |
3+| ❌ Breaking changes | ⚠️ Non-breaking changes |
4+|---------------------|-------------------------|
5+| 0 | 0 |
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/add/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|------------------------------------------|------------------------|-------------------------------|
5−| 1 | 0 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | ⚠️ Non-breaking changes |
4+|------------------------------------------|-------------------------|
5+| 1 | 0 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/change/report.md+7−7
@@ -1,24 +1,24 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 1 | 1 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 1 | 1 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱⬅️ JSON Response – 200
11+### ⬅️☁️ JSON Response – 200
1212
1313#### `$(Number)`
1414
1515The type has been added.
1616
17−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
17+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
1818
1919## **POST** /test
2020
21−### 📱⬅️ JSON Response – 200
21+### ⬅️☁️ JSON Response – 200
2222
2323#### `$(String)`
2424
test/golden/common/pathItem/operation/responses/change/mediaTypeObject/del/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| ⚠️ Breaking changes | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|---------------------|-------------------------------------------------|-------------------------------|
5−| 0 | 1 | 0 |
3+| ❌ Breaking changes | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|---------------------|--------------------------------------------------|
5+| 0 | 1 |
66
7−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
7+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
88
99## **POST** /test
1010
test/golden/common/pathItem/operation/responses/del/report.md+4−4
@@ -1,10 +1,10 @@
11# Summary
22
3−| ⚠️ Breaking changes | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|---------------------|-------------------------------------------------|-------------------------------|
5−| 0 | 1 | 0 |
3+| ❌ Breaking changes | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|---------------------|--------------------------------------------------|
5+| 0 | 1 |
66
7−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
7+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
88
99## **POST** /test
1010
test/golden/common/property-removed-additional/report.md+7−7
@@ -1,14 +1,14 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 3 | 3 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 3 | 3 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱⬅️ JSON Response – 200
11+### ⬅️☁️ JSON Response – 200
1212
1313#### `$.property2`
1414
@@ -21,11 +21,11 @@
2121
22223. The property may not be present.
2323
24−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
24+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
2525
2626## **POST** /test
2727
28−### 📱➡️ JSON Request
28+### ➡️☁️ JSON Request
2929
3030#### `$.property2`
3131
test/golden/common/property-removed/report.md+6−6
@@ -1,20 +1,20 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|------------------------------------------|------------------------|-------------------------------|
5−| 2 | 0 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | ⚠️ Non-breaking changes |
4+|------------------------------------------|-------------------------|
5+| 2 | 0 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313#### `$.property2`
1414
1515The property has been removed.
1616
17−### 📱⬅️ JSON Response – 200
17+### ⬅️☁️ JSON Response – 200
1818
1919#### `$.property2`
2020
test/golden/common/property-required/report.md+7−7
@@ -1,24 +1,24 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 1 | 1 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 1 | 1 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313#### `$.property2`
1414
1515The property has become required.
1616
17−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
17+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
1818
1919## **POST** /test
2020
21−### 📱⬅️ JSON Response – 200
21+### ⬅️☁️ JSON Response – 200
2222
2323#### `$.property2`
2424
test/golden/common/random-example/report.md+9−9
@@ -1,14 +1,14 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 5 | 6 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 5 | 6 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **GET** /pets
1010
11−### 📱⬅️ JSON Response – 200
11+### ⬅️☁️ JSON Response – 200
1212
1313#### `$[*].name(String)`
1414
@@ -18,7 +18,7 @@
1818
1919## **POST** /pets
2020
21−### 📱➡️ JSON Request
21+### ➡️☁️ JSON Request
2222
2323#### `$.weight`
2424
@@ -33,7 +33,7 @@
3333
3434Value is now a multiple of 1.0.
3535
36−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
36+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
3737
3838## **GET** /pets
3939
@@ -45,7 +45,7 @@ Value is now a multiple of 1.0.
4545
4646Upper bound changed from 20.0 inclusive to 30.0 inclusive.
4747
48−### 📱⬅️ JSON Response – 200
48+### ⬅️☁️ JSON Response – 200
4949
5050#### `$[*].weight`
5151
@@ -62,7 +62,7 @@ Value is now a multiple of 1.0.
6262
6363## **POST** /pets
6464
65−### 📱➡️ JSON Request
65+### ➡️☁️ JSON Request
6666
6767#### `$.name(String)`
6868
test/golden/common/recursive/report.md+7−7
@@ -1,14 +1,14 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 1 | 1 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 1 | 1 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **GET** /api/foo
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313#### `$.leaf.value.foo(String)`
1414
@@ -18,11 +18,11 @@ The following enum value has been added:
1818"a"
1919```
2020
21−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
21+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
2222
2323## **GET** /api/foo
2424
25−### 📱⬅️ JSON Response – 200
25+### ⬅️☁️ JSON Response – 200
2626
2727#### `$.leaf.value.foo(String)`
2828
test/golden/common/security-scheme/report.md+5−5
@@ -1,10 +1,10 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 2 | 1 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 2 | 1 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /oauth/check
1010
@@ -22,7 +22,7 @@ Security scheme has been removed.
2222
2323Security scheme has been removed.
2424
25−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
25+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
2626
2727## **POST** /oauth/check
2828
test/golden/common/servers/report.md+5−5
@@ -1,10 +1,10 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 3 | 1 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 3 | 1 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **GET** /pets
1010
@@ -18,7 +18,7 @@ The server was removed.
1818
19192. A variable has been changed from being open to being closed.
2020
21−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
21+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
2222
2323## **GET** /pets
2424
test/golden/common/tuple-items/heterogeneous-component-mismatch/report.md+9−9
@@ -1,36 +1,36 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 2 | 2 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 2 | 2 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313#### `$[1](String)`
1414
1515The type has been removed.
1616
17−### 📱⬅️ JSON Response – 200
17+### ⬅️☁️ JSON Response – 200
1818
1919#### `$[1](Object)`
2020
2121The type has been added.
2222
23−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
23+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
2424
2525## **POST** /test
2626
27−### 📱➡️ JSON Request
27+### ➡️☁️ JSON Request
2828
2929#### `$[1](Object)`
3030
3131The type has been added.
3232
33−### 📱⬅️ JSON Response – 200
33+### ⬅️☁️ JSON Response – 200
3434
3535#### `$[1](String)`
3636
test/golden/common/tuple-items/heterogeneous-length-mismatch/report.md+6−6
@@ -1,20 +1,20 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|------------------------------------------|------------------------|-------------------------------|
5−| 2 | 0 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | ⚠️ Non-breaking changes |
4+|------------------------------------------|-------------------------|
5+| 2 | 0 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313#### `$(Array)`
1414
1515Tuple length changed from 3 to 2.
1616
17−### 📱⬅️ JSON Response – 200
17+### ⬅️☁️ JSON Response – 200
1818
1919#### `$(Array)`
2020
test/golden/common/tuple-items/homogeneous-component-mismatch/report.md+9−9
@@ -1,14 +1,14 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 4 | 2 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 4 | 2 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313#### `$(Array)`
1414
@@ -18,7 +18,7 @@ The array is no longer explicitly defined as a tuple.
1818
1919The type has been removed.
2020
21−### 📱⬅️ JSON Response – 200
21+### ⬅️☁️ JSON Response – 200
2222
2323#### `$(Array)`
2424
@@ -28,17 +28,17 @@ The array is no longer explicitly defined as a tuple.
2828
2929The type has been added.
3030
31−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
31+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
3232
3333## **POST** /test
3434
35−### 📱➡️ JSON Request
35+### ➡️☁️ JSON Request
3636
3737#### `$[0](Number)`
3838
3939The type has been added.
4040
41−### 📱⬅️ JSON Response – 200
41+### ⬅️☁️ JSON Response – 200
4242
4343#### `$[0](String)`
4444
test/golden/common/tuple-items/homogeneous-length-mismatch/report.md+13−13
@@ -1,14 +1,14 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 5 | 5 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 5 | 5 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test1
1010
11−### 📱⬅️ JSON Response – 200
11+### ⬅️☁️ JSON Response – 200
1212
1313#### `$(Array)`
1414
@@ -16,7 +16,7 @@ The array is no longer explicitly defined as a tuple.
1616
1717## **POST** /test2
1818
19−### 📱⬅️ JSON Response – 200
19+### ⬅️☁️ JSON Response – 200
2020
2121#### `$(Array)`
2222
@@ -24,13 +24,13 @@ The array is no longer explicitly defined as a tuple.
2424
2525## **POST** /test3
2626
27−### 📱➡️ JSON Request
27+### ➡️☁️ JSON Request
2828
2929#### `$(Array)`
3030
3131Minimum length of the array changed from 2 to 3.
3232
33−### 📱⬅️ JSON Response – 200
33+### ⬅️☁️ JSON Response – 200
3434
3535#### `$(Array)`
3636
@@ -38,11 +38,11 @@ Minimum length of the array changed from 2 to 3.
3838
39392. The array is no longer explicitly defined as a tuple.
4040
41−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
41+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
4242
4343## **POST** /test1
4444
45−### 📱➡️ JSON Request
45+### ➡️☁️ JSON Request
4646
4747#### `$(Array)`
4848
@@ -50,7 +50,7 @@ The array is no longer explicitly defined as a tuple.
5050
5151## **POST** /test2
5252
53−### 📱➡️ JSON Request
53+### ➡️☁️ JSON Request
5454
5555#### `$(Array)`
5656
@@ -58,7 +58,7 @@ The array is no longer explicitly defined as a tuple.
5858
5959## **POST** /test3
6060
61−### 📱➡️ JSON Request
61+### ➡️☁️ JSON Request
6262
6363#### `$(Array)`
6464
@@ -66,7 +66,7 @@ The array is no longer explicitly defined as a tuple.
6666
67672. The array is no longer explicitly defined as a tuple.
6868
69−### 📱⬅️ JSON Response – 200
69+### ⬅️☁️ JSON Response – 200
7070
7171#### `$(Array)`
7272
test/golden/common/tuple-items/homogeneous-vector/report.md+3−3
@@ -1,5 +1,5 @@
11# Summary
22
3−| ⚠️ Breaking changes | 🙆 Non-breaking changes | 🤷 Unsupported feature changes |
4−|---------------------|------------------------|-------------------------------|
5−| 0 | 0 | 0 |
3+| ❌ Breaking changes | ⚠️ Non-breaking changes |
4+|---------------------|-------------------------|
5+| 0 | 0 |
test/golden/common/type-changing/report.md+15−15
@@ -1,14 +1,14 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 6 | 6 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 6 | 6 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test1
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313Values are now limited to the following types:
1414
@@ -16,7 +16,7 @@ Values are now limited to the following types:
1616
1717## **POST** /test2
1818
19−### 📱➡️ JSON Request
19+### ➡️☁️ JSON Request
2020
2121Values are now limited to the following types:
2222
@@ -28,7 +28,7 @@ Values are now limited to the following types:
2828
2929## **POST** /test3
3030
31−### 📱➡️ JSON Request
31+### ➡️☁️ JSON Request
3232
3333#### `$(Array)`
3434
@@ -36,7 +36,7 @@ The type has been removed.
3636
3737## **POST** /test4
3838
39−### 📱➡️ JSON Request
39+### ➡️☁️ JSON Request
4040
4141#### `$(Object)`
4242
@@ -44,7 +44,7 @@ The type has been removed.
4444
4545## **POST** /test5
4646
47−### 📱➡️ JSON Request
47+### ➡️☁️ JSON Request
4848
4949Values are now limited to the following types:
5050
@@ -54,11 +54,11 @@ Values are now limited to the following types:
5454
5555Value is now a multiple of 1.0.
5656
57−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
57+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
5858
5959## **POST** /test1
6060
61−### 📱⬅️ JSON Response – 200
61+### ⬅️☁️ JSON Response – 200
6262
6363Values are now limited to the following types:
6464
@@ -66,7 +66,7 @@ Values are now limited to the following types:
6666
6767## **POST** /test2
6868
69−### 📱⬅️ JSON Response – 200
69+### ⬅️☁️ JSON Response – 200
7070
7171Values are now limited to the following types:
7272
@@ -78,7 +78,7 @@ Values are now limited to the following types:
7878
7979## **POST** /test3
8080
81−### 📱⬅️ JSON Response – 200
81+### ⬅️☁️ JSON Response – 200
8282
8383#### `$(Array)`
8484
@@ -86,7 +86,7 @@ The type has been removed.
8686
8787## **POST** /test4
8888
89−### 📱⬅️ JSON Response – 200
89+### ⬅️☁️ JSON Response – 200
9090
9191#### `$(Object)`
9292
@@ -94,7 +94,7 @@ The type has been removed.
9494
9595## **POST** /test5
9696
97−### 📱⬅️ JSON Response – 200
97+### ⬅️☁️ JSON Response – 200
9898
9999Values are now limited to the following types:
100100
test/golden/common/unguarded-recursive/report.md+6−6
@@ -1,13 +1,13 @@
11# Summary
22
3−| ⚠️ Breaking changes | 🙆 Non-breaking changes | [🤷 Unsupported feature changes](#unsupported-changes) |
4−|---------------------|------------------------|-------------------------------------------------------|
5−| 0 | 0 | 1 |
3+| ❌ Breaking changes | ⚠️ Non-breaking changes | [❓ Unsupported feature changes](#unsupported-changes) |
4+|---------------------|-------------------------|--------------------------------------------------------|
5+| 0 | 0 | 1 |
66
7−# <span id="unsupported-changes"></span>🤷 Unsupported feature changes
7+# <span id="unsupported-changes"></span>❓ Unsupported feature changes
88
99## **GET** /api/foo
1010
11−### 📱⬅️ JSON Response – 200
11+### ⬅️☁️ JSON Response – 200
1212
13−Encountered recursion that is too complex for OpenApi Diff to untangle.
13+Encountered recursion that is too complex for CompaREST to untangle.
test/golden/common/variant-added/report.md+7−7
@@ -1,24 +1,24 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 1 | 1 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 1 | 1 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱⬅️ JSON Response – 200
11+### ⬅️☁️ JSON Response – 200
1212
1313#### `$(Object)`
1414
1515The case where `$.tag` is `"C"` – has been added.
1616
17−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
17+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
1818
1919## **POST** /test
2020
21−### 📱➡️ JSON Request
21+### ➡️☁️ JSON Request
2222
2323#### `$(Object)`
2424
test/golden/common/variant-changed/report.md+9−9
@@ -1,14 +1,14 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 2 | 2 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 2 | 2 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313#### In cases where `$.tag` is `"B"`
1414
@@ -16,7 +16,7 @@
1616
1717The type has been removed.
1818
19−### 📱⬅️ JSON Response – 200
19+### ⬅️☁️ JSON Response – 200
2020
2121#### In cases where `$.tag` is `"B"`
2222
@@ -24,11 +24,11 @@ The type has been removed.
2424
2525The type has been added.
2626
27−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
27+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
2828
2929## **POST** /test
3030
31−### 📱➡️ JSON Request
31+### ➡️☁️ JSON Request
3232
3333#### In cases where `$.tag` is `"B"`
3434
@@ -36,7 +36,7 @@ The type has been added.
3636
3737The type has been added.
3838
39−### 📱⬅️ JSON Response – 200
39+### ⬅️☁️ JSON Response – 200
4040
4141#### In cases where `$.tag` is `"B"`
4242
test/golden/common/variant-deep-changed/report.md+9−9
@@ -1,14 +1,14 @@
11# Summary
22
3−| [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
4−|------------------------------------------|-------------------------------------------------|-------------------------------|
5−| 2 | 2 | 0 |
3+| [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
4+|------------------------------------------|--------------------------------------------------|
5+| 2 | 2 |
66
7−# <span id="breaking-changes"></span>⚠️ Breaking changes
7+# <span id="breaking-changes"></span>❌ Breaking changes
88
99## **POST** /test
1010
11−### 📱➡️ JSON Request
11+### ➡️☁️ JSON Request
1212
1313#### In cases where `$.desc.name` is `"B"`
1414
@@ -16,7 +16,7 @@
1616
1717The type has been removed.
1818
19−### 📱⬅️ JSON Response – 200
19+### ⬅️☁️ JSON Response – 200
2020
2121#### In cases where `$.desc.name` is `"B"`
2222
@@ -24,11 +24,11 @@ The type has been removed.
2424
2525The type has been added.
2626
27−# <span id="non-breaking-changes"></span>🙆 Non-breaking changes
27+# <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
2828
2929## **POST** /test
3030
31−### 📱➡️ JSON Request
31+### ➡️☁️ JSON Request
3232
3333#### In cases where `$.desc.name` is `"B"`
3434
@@ -36,7 +36,7 @@ The type has been added.
3636
3737The type has been added.
3838
39−### 📱⬅️ JSON Response – 200
39+### ⬅️☁️ JSON Response – 200
4040
4141#### In cases where `$.desc.name` is `"B"`
4242