git.iko.soy/typeable/compaREST
on master help sign in
40 files changed+343−192
since splitsnapshots
unifiedsplit
.github/actions/common_setup/action.yaml+2−2 .github/workflows/github-actions-image.yaml+60−81 .gitmodules−3 CHANGELOG.md+7−3 LICENSE+1−1 README.md+40−19 action.yaml+9−8 app/Data/OpenApi/Compare/Options.hs+1 app/Main.hs+1 awsm-css−1 compaREST.cabal+6−4 css/awsm.min.css+1 default.nix+18−7 docs/Integration_guide.md−2 docs/Releasing.md+15 docs/img/pre-release-artifacts.png docs/img/pre-release-ci.png docs/img/pre-release.png github-action/CompaREST/GitHub/API.hs+2−2 github-action/CompaREST/GitHub/Action/Config.hs+7−3 github-action/Main.hs+13−7 nix/ci.nix+6−1 nix/diff-yaml.nix+20 nix/shell.nix+11 nix/sources.json+13−13 nix/yq.nix+26 policeman.json−6 scripts/diff-yaml+35 src/Data/OpenApi/Compare/PathsPrefixTree.hs+5−4 src/Data/OpenApi/Compare/Report.hs+1−1 src/Data/OpenApi/Compare/Report/Html/Template.hs+1−1 src/Data/OpenApi/Compare/Validate/OpenApi.hs+2−1 src/Data/OpenApi/Compare/Validate/Schema.hs+1−1 src/Data/OpenApi/Compare/Validate/Schema/JsonFormula.hs+7−6 src/Data/OpenApi/Compare/Validate/Schema/Partition.hs+4−1 src/Data/OpenApi/Compare/Validate/Schema/Process.hs+2 stack.yaml+16−4 stack.yaml.lock+4−4 test/Spec/Golden/Extra.hs+2−2 test/golden/common/random-example/trace-tree.yaml+4−4
.github/actions/common_setup/action.yaml+2−2
@@ -30,13 +30,13 @@ runs:
3030 fi
3131
3232 - name: Install Nix
33− uses: cachix/install-nix-action@v16
33+ uses: cachix/install-nix-action@v27
3434 with:
3535 extra_nix_config: |
3636 system-features = kvm
3737 trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
3838 substituters = https://hydra.iohk.io https://cache.nixos.org/
39− - uses: cachix/cachix-action@v10
39+ - uses: cachix/cachix-action@v15
4040 with:
4141 name: typeable
4242 authToken: "${{ inputs.authToken }}"
.github/workflows/github-actions-image.yaml+60−81
@@ -9,23 +9,23 @@ on:
99 types: [prereleased]
1010
1111jobs:
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 }}
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
2227
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:
28+ check_versions:
2929 runs-on: ubuntu-latest
3030 steps:
3131 - uses: actions/checkout@v2
@@ -37,25 +37,6 @@ jobs:
3737 with:
3838 authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
3939
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−
5940 - name: Check library version
6041 if: github.event_name == 'release'
6142 run: |
@@ -127,13 +108,6 @@ jobs:
127108 name: compaREST Docker Image
128109 path: compaREST-image.tar.gz
129110
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−
137111 - name: Login to DockerHub
138112 uses: docker/login-action@v1.10.0
139113 with:
@@ -185,13 +159,18 @@ jobs:
185159 tag: ${{ github.ref }}
186160
187161 dist:
188− needs: check_versions
162+ needs: [check_versions, Linux]
189163 runs-on: ubuntu-latest
190164 steps:
191165 - uses: actions/checkout@v2
192166 with:
193167 submodules: recursive
194168
169+ - name: Common setup
170+ uses: ./.github/actions/common_setup
171+ with:
172+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
173+
195174 - name: Cache
196175 uses: actions/cache@v1
197176 with:
@@ -210,7 +189,7 @@ jobs:
210189
211190 - name: Build dist
212191 run: |
213− stack sdist --pvp-bounds both --test-tarball
192+ stack sdist --pvp-bounds both
214193 echo "distPath=$(stack path | awk -F ": " '/dist-dir/ {print $2}')" >> $GITHUB_ENV
215194
216195 - name: Upload compaREST sources artifact
@@ -234,7 +213,7 @@ jobs:
234213 env:
235214 HACKAGE_KEY: ${{ secrets.HACKAGE_KEY }}
236215 run: |
237− stack upload --pvp-bounds both --test-tarball --candidate .
216+ stack upload --pvp-bounds both --candidate .
238217
239218 macOS:
240219 needs: check_versions
@@ -273,39 +252,39 @@ jobs:
273252 file: compaREST-macOS-x86.zip
274253 tag: ${{ github.ref }}
275254
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 }}
255+# Linux-Windows:
256+# needs: check_versions
257+# runs-on: ubuntu-latest
258+# steps:
259+# - uses: actions/checkout@v2
260+# with:
261+# submodules: recursive
262+#
263+# - name: Common setup
264+# uses: ./.github/actions/common_setup
265+# with:
266+# authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
267+#
268+# - name: Build Windows binary
269+# run: |
270+# cp -LR $(nix-build -A WindowsCompaRESTBin -j auto --no-out-link)/bin compaREST
271+#
272+# - name: Upload Windows compaREST bundle
273+# uses: actions/upload-artifact@v2
274+# with:
275+# name: compaREST binary – Windows
276+# path: compaREST
277+#
278+# - name: Zip Release Assets
279+# if: github.event_name == 'release'
280+# shell: bash
281+# run: |
282+# zip -r compaREST-Windows.zip compaREST
283+#
284+# - name: Upload compaREST binary – Windows to release
285+# if: github.event_name == 'release'
286+# uses: svenstaro/upload-release-action@2.2.1
287+# with:
288+# repo_token: ${{ secrets.GITHUB_TOKEN }}
289+# file: compaREST-Windows.zip
290+# tag: ${{ github.ref }}
.gitmodulesdeleted−3
@@ -1,3 +0,0 @@
1−[submodule "awsm-css"]
2− path = awsm-css
3− url = https://github.com/igoradamenko/awsm.css.git
CHANGELOG.md+7−3
@@ -1,9 +1,13 @@
11# Changelog
22
3−`comparest` uses [PVP Versioning][1].
3+`compaREST` uses [PVP Versioning][1].
44
5−## 0.0.0.0
5+## 0.1.0.1
66
7−* Initially created.
7+* Fixed an issue related to empty commit SHAs in GitHub Actions.
8+
9+## 0.1.0.0
10+
11+* Initial release.
812
913[1]: https://pvp.haskell.org
LICENSE+1−1
@@ -1,6 +1,6 @@
11MIT License
22
3−Copyright (c) 2021 Aleksey Uymanov
3+Copyright (c) 2021 Typeable
44
55Permission is hereby granted, free of charge, to any person obtaining a copy
66of this software and associated documentation files (the "Software"), to deal
README.md+40−19
@@ -1,14 +1,33 @@
1−# CompaREST
1+# compaREST
22
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)
3+[![Hackage](https://img.shields.io/hackage/v/compaREST.svg?logo=haskell)](https://hackage.haskell.org/package/compaREST)
64[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
75
86Compatibility checker for OpenAPI
9−# Quick Start Guide
107
11−## Your situation
8+## Using compaREST in Github Actions
9+
10+Add the following step to your Github Actions workflow:
11+
12+```yaml
13+- uses: typeable/comparest
14+ if: ${{ github.event_name == 'pull_request' }}
15+ with:
16+ old: old-openapi.yaml
17+ new: new-openapi.yaml
18+```
19+
20+The `new` and `old` values should be paths to your OpenAPI specifications you want to compare.
21+
22+You will get something like this in your pull requests:
23+
24+![](docs/img/github-action-report.png)
25+
26+For more detail please see our [integration guide](docs/Integration_guide.md).
27+
28+## An example
29+
30+### Your situation
1231
1332You 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.)
1433
@@ -75,7 +94,7 @@ components:
7594 $ref: "#/components/schemas/Pet"
7695```
7796
78−## Evolving your product
97+### Evolving your product
7998
8099Enthused over your initial success you hurry to release a new and improved version of your API and mobile app.
81100
@@ -144,7 +163,7 @@ components:
144163
145164Looking 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.
146165
147−## Assessing compatibility automatically
166+### Assessing compatibility automatically
148167
149168Luckily, you have access to compaREST which can programmatically analyze your APIs and determine what, if anything, breaks compatibility and what doesn't.
150169
@@ -158,15 +177,15 @@ Running this command will output a file `report.md`, containing the compatibilit
158177
159178> # Summary
160179>
161−> | [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
162−> |------------------------------------------|-------------------------------------------------|-------------------------------|
163−> | 5 | 6 | 0 |
180+> | [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
181+> |------------------------------------------|--------------------------------------------------|
182+> | 5 | 6 |
164183>
165−> # <span id="breaking-changes"></span>⚠️ Breaking changes
184+> # <span id="breaking-changes"></span>❌ Breaking changes
166185>
167186> ## **GET** /pets
168187>
169−> ### 📱⬅️ JSON Response – 200
188+> ### ⬅️☁️ JSON Response – 200
170189>
171190> #### `$[*].name(String)`
172191>
@@ -176,7 +195,7 @@ Running this command will output a file `report.md`, containing the compatibilit
176195>
177196> ## **POST** /pets
178197>
179−> ### 📱➡️ JSON Request
198+> ### ➡️☁️ JSON Request
180199>
181200> #### `$.weight`
182201>
@@ -191,7 +210,7 @@ Running this command will output a file `report.md`, containing the compatibilit
191210>
192211> Value is now a multiple of 1.0.
193212>
194−> # <span id="non-breaking-changes"></span>🙆 Non-breaking changes
213+> # <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
195214>
196215> ## **GET** /pets
197216>
@@ -203,7 +222,7 @@ Running this command will output a file `report.md`, containing the compatibilit
203222>
204223> Upper bound changed from 20.0 inclusive to 30.0 inclusive.
205224>
206−> ### 📱⬅️ JSON Response – 200
225+> ### ⬅️☁️ JSON Response – 200
207226>
208227> #### `$[*].weight`
209228>
@@ -220,7 +239,7 @@ Running this command will output a file `report.md`, containing the compatibilit
220239>
221240> ## **POST** /pets
222241>
223−> ### 📱➡️ JSON Request
242+> ### ➡️☁️ JSON Request
224243>
225244> #### `$.name(String)`
226245>
@@ -230,7 +249,7 @@ Running this command will output a file `report.md`, containing the compatibilit
230249
231250You 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.
232251
233−## Additional formats
252+### Additional formats
234253
235254You 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:
236255
@@ -238,7 +257,9 @@ You can also produce a self-contained HTML report that you can open in your brow
238257docker 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
239258```
240259
241−# CLI docs
260+## CLI docs
261+
262+For more detail please see our [user guide](docs/User_guide.md).
242263
243264```
244265Usage: comparest (-c|--client ARG) (-s|--server ARG)
action.yaml+9−8
@@ -7,21 +7,22 @@ inputs:
77 default: "${{ github.token }}"
88 required: false
99 repo:
10− description: The owner of the repo in which to post the comment.
10+ description: The owner of the repo in which to post the results.
1111 default: "${{ github.repository }}"
1212 required: false
1313 project_name:
14− description: The name of the project to which the API pertains.
15− required: true
14+ description: The name of the project to which the API pertains. Used to name the check. Useful when you have multiple APIs in the same repo.
15+ required: false
16+ default: ""
1617 footer:
17− description: A footer that can be appended to the comment.
18+ description: A footer that can be appended to the check body.
1819 required: false
1920 default: ""
2021 old:
21− description: The path to old specification of the API.
22+ description: The path to the old (client) specification of the API.
2223 required: true
2324 new:
24− description: The path to new specification of the API.
25+ description: The path to new (server) specification of the API.
2526 required: true
2627 sha:
2728 description: The sha of the commit to post the check for.
@@ -29,7 +30,7 @@ inputs:
2930 default: "${{ github.event.pull_request.head.sha }}"
3031runs:
3132 using: "docker"
32− image: "docker://typeable/comparest-github-action:latest"
33+ image: "docker://typeable/comparest-github-action:0.1.0.1"
3334 env:
3435 GITHUB_TOKEN: "${{ inputs.GITHUB_TOKEN }}"
3536 REPO: "${{ inputs.repo }}"
@@ -44,4 +45,4 @@ runs:
4445
4546branding:
4647 icon: crosshair
47− color: gray-dark
48+ color: blue
app/Data/OpenApi/Compare/Options.hs+1
@@ -9,6 +9,7 @@ import Data.OpenApi.Compare.Report
99import GHC.Generics (Generic)
1010import Options.Applicative
1111import Options.Applicative.Help hiding (fullDesc)
12+import Text.PrettyPrint.ANSI.Leijen ((<$$>),string)
1213
1314parseOptions :: IO Options
1415parseOptions = customExecParser (prefs $ showHelpOnError) optionsParserInfo
app/Main.hs+1
@@ -2,6 +2,7 @@ module Main (main) where
22
33import Control.Monad
44import Control.Monad.Except
5+import Control.Monad.Trans (lift)
56import Data.Aeson
67import qualified Data.ByteString.Lazy as BSL
78import Data.Default
awsm-cssdeleted−1

Submodule changed.

compaREST.cabal+6−4
@@ -1,6 +1,6 @@
11cabal-version: 2.4
22name: compaREST
3−version: 0.0.0.0
3+version: 0.1.0.1
44synopsis: Compatibility checker for OpenAPI
55description: Compatibility checker for OpenAPI.
66license: MIT
@@ -12,8 +12,8 @@ category: Web
1212build-type: Simple
1313extra-doc-files: README.md
1414 CHANGELOG.md
15−tested-with: GHC == 8.10.4
16−extra-source-files: awsm-css/dist/awsm.min.css
15+tested-with: GHC == 9.6
16+extra-source-files: css/awsm.min.css
1717extra-source-files:
1818 test/golden/**/*.yaml
1919 test/golden/**/*.md
@@ -26,13 +26,14 @@ common common-options
2626 -Wno-safe
2727 -Wno-prepositive-qualified-module
2828 -Wno-missing-import-lists
29+ -Wno-missing-kind-signatures
2930 -Wno-partial-fields
3031 -Wno-all-missed-specialisations
3132 -Wno-missing-local-signatures
3233 -Wno-unsafe
3334 -fconstraint-solver-iterations=0
3435 default-language: Haskell2010
35− build-depends: base >= 4.12.0.0 && < 4.16
36+ build-depends: base >= 4.12.0.0 && < 5
3637 , text
3738
3839
@@ -165,6 +166,7 @@ executable compaREST
165166 , containers
166167 , doctemplates
167168 , pandoc-types
169+ , prettyprinter-compat-ansi-wl-pprint
168170 ghc-options: -threaded
169171 -rtsopts
170172 -with-rtsopts=-N
css/awsm.min.cssnew file+1
@@ -0,0 +1 @@
1+@charset "UTF-8";html{font-family:"PT Serif",Georgia,serif;font-size:100%;line-height:1.6;background:#fff;color:#000;-webkit-overflow-scrolling:touch}body{margin:1em;font-size:1rem}@media (min-width:20rem){body{font-size:calc(1rem + .0125*(100vw - 20rem))}}@media (min-width:40rem){body{font-size:1.25rem}}body article,body footer,body header,body main{position:relative;max-width:40rem;margin:0 auto}body>header{margin-bottom:3.5em}body>header h1{margin:0;font-size:1.5em}body>header p{margin:0;font-size:.85em}body>footer{margin-top:4.5em;padding-bottom:1.5em;text-align:center;font-size:.8rem;color:#aaa}details,nav,p{margin:1em 0}nav ul{list-style:none;margin:0;padding:0}nav li{display:inline-block;margin-right:1em;margin-bottom:.25em}nav a:visited{color:#0064c1}article header h1 a:visited:hover,article header h2 a:visited:hover,nav a:hover{color:#3000c1}ol,ul{margin-top:0;padding-top:0;padding-left:2.5em}p{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto}aside:first-child,p:first-child{margin-top:0}aside:last-child,p:last-child{margin-bottom:0}p+ol,p+ul{margin-top:-.75em}p img,p picture{float:right;margin-bottom:.5em;margin-left:.5em}p picture img{float:none;margin:0}blockquote,dd{padding-left:2.5em}dd{margin-bottom:1em;margin-left:0}dt{font-weight:700}blockquote{margin:0}aside{margin:.5em 0;font-style:italic;color:#aaa}@media (min-width:65rem){aside{position:absolute;right:-12.5rem;width:9.375rem;max-width:9.375rem;margin:0;padding-left:.5em;font-size:.8em;border-left:1px solid #f2f2f2}}section+section{margin-top:2em}h1,h2,h3,h4,h5,h6{margin:1.25em 0 0;line-height:1.2}h1:focus>a[href^="#"][id],h1:hover>a[href^="#"][id],h2:focus>a[href^="#"][id],h2:hover>a[href^="#"][id],h3:focus>a[href^="#"][id],h3:hover>a[href^="#"][id],h4:focus>a[href^="#"][id],h4:hover>a[href^="#"][id],h5:focus>a[href^="#"][id],h5:hover>a[href^="#"][id],h6:focus>a[href^="#"][id],h6:hover>a[href^="#"][id]{opacity:1;transition:opacity 0s,color 300ms ease-out}figure+p,h1+details,h1+p,h2+details,h2+p,h3+details,h3+p,h4+details,h4+p,h5+details,h5+p,h6+details,h6+p{margin-top:.5em}h1>a[href^="#"][id],h2>a[href^="#"][id],h3>a[href^="#"][id],h4>a[href^="#"][id],h5>a[href^="#"][id],h6>a[href^="#"][id]{position:absolute;left:-.5em;opacity:0;background:0 0;color:rgba(0,0,0,.5);transition:opacity 300ms ease-out}h1>a[href^="#"][id]:focus,h1>a[href^="#"][id]:hover,h1>a[href^="#"][id]:target,h2>a[href^="#"][id]:focus,h2>a[href^="#"][id]:hover,h2>a[href^="#"][id]:target,h3>a[href^="#"][id]:focus,h3>a[href^="#"][id]:hover,h3>a[href^="#"][id]:target,h4>a[href^="#"][id]:focus,h4>a[href^="#"][id]:hover,h4>a[href^="#"][id]:target,h5>a[href^="#"][id]:focus,h5>a[href^="#"][id]:hover,h5>a[href^="#"][id]:target,h6>a[href^="#"][id]:focus,h6>a[href^="#"][id]:hover,h6>a[href^="#"][id]:target{opacity:1;box-shadow:none;color:#000;transition:opacity 0s,color 0s}h1>a[href^="#"][id]:target:focus,h2>a[href^="#"][id]:target:focus,h3>a[href^="#"][id]:target:focus,h4>a[href^="#"][id]:target:focus,h5>a[href^="#"][id]:target:focus,h6>a[href^="#"][id]:target:focus{outline:0}h1>a[href^="#"][id]::before,h2>a[href^="#"][id]::before,h3>a[href^="#"][id]::before,h4>a[href^="#"][id]::before,h5>a[href^="#"][id]::before,h6>a[href^="#"][id]::before{content:"•"}h1{font-size:2em}h2{font-size:1.75em}h3{font-size:1.25em}h4{font-size:1.15em}h5,h6{font-size:1em}h6{margin-top:1em;color:#aaa}article+article{margin-top:5em}article header p{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:.6em;color:#aaa}article header p+h1,article header p+h2{margin-top:-.25em}article header h1+p,article header h2+p{margin-top:.25em}article header h1 a,article header h2 a{color:#000;background:url('data:image/svg+xml;charset=utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="1" height="1"%3E%3Crect x="0" y="0.5" width="1" height="0.5" fill="rgba(0, 0, 0, 0.35)"/%3E%3C/svg%3E') left bottom repeat-x}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){article header h1 a,article header h2 a{background:url('data:image/svg+xml;charset=utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="1" height="2"%3E%3Crect x="0" y="1" width="1" height="1" fill="rgba(0, 0, 0, 0.35)"/%3E%3C/svg%3E') left bottom repeat-x}}article header h1 a:visited,article header h2 a:visited{color:#aaa;background:url('data:image/svg+xml;charset=utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="1" height="1"%3E%3Crect x="0" y="0.5" width="1" height="0.5" fill="rgba(170, 170, 170, 0.35)"/%3E%3C/svg%3E') left bottom repeat-x}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){article header h1 a:visited,article header h2 a:visited{background:url('data:image/svg+xml;charset=utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="1" height="2"%3E%3Crect x="0" y="1" width="1" height="1" fill="rgba(170, 170, 170, 0.35)"/%3E%3C/svg%3E') left bottom repeat-x}}article>footer{margin-top:1.5em;font-size:.85em}a,abbr{text-decoration:none}a{color:#0064c1;transition:color 300ms ease-out,box-shadow 300ms ease-out;background:url('data:image/svg+xml;charset=utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="1" height="1"%3E%3Crect x="0" y="0.5" width="1" height="0.5" fill="rgba(0, 100, 193, 0.35)"/%3E%3C/svg%3E') left bottom repeat-x}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){a{background:url('data:image/svg+xml;charset=utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" width="1" height="2"%3E%3Crect x="0" y="1" width="1" height="1" fill="rgba(0, 100, 193, 0.35)"/%3E%3C/svg%3E') left bottom repeat-x}}a:visited{color:#80b2e0}a:active,a:hover{outline-width:0}a:hover{box-shadow:inset 0 -1px #3000c1;color:#3000c1;transition:color 0s,box-shadow 0s}abbr{margin-right:-.075em;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;letter-spacing:.075em;font-size:.9em;border-bottom:1px dotted}img,picture{display:block;max-width:100%;margin:0 auto}audio,video{width:100%;max-width:100%}video{-o-object-position:0 -1px;object-position:0 -1px}figure{margin:1em 0 .5em;padding:0}figure figcaption{opacity:.65;font-size:.85em}table{display:inline-block;border-spacing:0;border-collapse:collapse;overflow-x:auto;max-width:100%;text-align:left;vertical-align:top;background:linear-gradient(rgba(0,0,0,.15) 0%,rgba(0,0,0,.15) 100%) 0 0,linear-gradient(rgba(0,0,0,.15) 0%,rgba(0,0,0,.15) 100%) 100% 0;background-attachment:scroll,scroll;background-size:1px 100%,1px 100%;background-repeat:no-repeat,no-repeat}table caption{font-size:.9em;background:#fff}table td,table th{padding:.35em .75em;vertical-align:top;font-size:.9em;border:1px solid #f2f2f2;border-top:0;border-left:0}table td:first-child,table th:first-child{padding-left:0;background-image:linear-gradient(to right,#fff 50%,rgba(255,255,255,0) 100%);background-size:2px 100%;background-repeat:no-repeat}table td:last-child,table th:last-child{padding-right:0;border-right:0;background-image:linear-gradient(to left,#fff 50%,rgba(255,255,255,0) 100%);background-position:100% 0;background-size:2px 100%;background-repeat:no-repeat}table td:only-child,table th:only-child{background-image:linear-gradient(to right,#fff 50%,rgba(255,255,255,0) 100%),linear-gradient(to left,#fff 50%,rgba(255,255,255,0) 100%);background-position:0 0,100% 0;background-size:2px 100%,2px 100%;background-repeat:no-repeat,no-repeat}table th{line-height:1.2}code,kbd,samp,var{font-family:Consolas,"Lucida Console",Monaco,monospace;font-style:normal}pre{overflow-x:auto;font-size:.8em;background:linear-gradient(rgba(0,0,0,.15) 0%,rgba(0,0,0,.15) 100%) 0 0,linear-gradient(rgba(0,0,0,.15) 0%,rgba(0,0,0,.15) 100%) 100% 0;background-attachment:scroll,scroll;background-size:1px 100%,1px 100%;background-repeat:no-repeat,no-repeat}pre>code,summary{display:inline-block}pre>code{overflow-x:visible;box-sizing:border-box;min-width:100%;border-right:3px solid #fff;border-left:1px solid #fff}hr{height:1px;margin:2em 0;border:0;background:#f2f2f2}details[open]{padding-bottom:.5em;border-bottom:1px solid #f2f2f2}summary{font-weight:700;border-bottom:1px dashed;cursor:pointer}summary::-webkit-details-marker{display:none}noscript{color:#d00000}::selection{background:rgba(0,100,193,.15)}
default.nix+18−7
@@ -1,11 +1,13 @@
11{ sources ? import ./nix/sources.nix
22, haskellNix ? import sources.haskellNix { inherit system; }
3−, pkgs ? import haskellNix.sources.nixpkgs-2105 (haskellNix.nixpkgsArgs // { inherit system; })
3+, pkgs ? import haskellNix.sources.nixpkgs-2311 (haskellNix.nixpkgsArgs // { inherit system; })
44, system ? builtins.currentSystem
55, nix-filter ? import sources.nix-filter
66}:
77let
88 masterPkgs = import sources.nixpkgs { inherit system; };
9+ yq = import ./nix/yq.nix { pkgs = masterPkgs; };
10+ diffYaml = import ./nix/diff-yaml.nix { pkgs = masterPkgs; };
911
1012 hsPkgs = pkgs.haskell-nix.stackProject {
1113 src = nix-filter {
@@ -38,7 +40,7 @@ let
3840 (inDirectory ./src)
3941 (inDirectory ./app)
4042 (inDirectory ./github-action)
41− ./awsm-css/dist/awsm.min.css
43+ ./css/awsm.min.css
4244 ./LICENSE
4345 ];
4446 };
@@ -97,6 +99,18 @@ let
9799 };
98100
99101 WindowsCompaRESTBin = hsPkgs.projectCross.mingwW64.hsPkgs.compaREST.components.exes.compaREST;
102+
103+ # We use the static version so that we don't have to rebuild everything on CI.
104+ # The only binaries build on CI are static.
105+ test = hsPkgs.projectCross.musl64.hsPkgs.compaREST.components.tests.compaREST-tests.overrideAttrs (final: old: {
106+ buildInputs = old.buildInputs ++ [ diffYaml yq ];
107+ nativeBuildInputs = old.nativeBuildInputs ++ (with masterPkgs; [ makeWrapper ]);
108+
109+ postInstall = ''
110+ wrapProgram $out/bin/compaREST-tests \
111+ --prefix PATH : ${masterPkgs.lib.makeBinPath [ diffYaml yq ]}
112+ '';
113+ });
100114in
101115{
102116 inherit
@@ -106,9 +120,6 @@ in
106120 compaRESTBin
107121 hsPkgs
108122 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;
123+ WindowsCompaRESTBin
124+ test;
114125}
docs/Integration_guide.md−2
@@ -28,8 +28,6 @@ In our example we have already generated the two OpenAPI specs and placed them i
2828
2929We can now run our action:
3030
31−##### NOTE: will only work after public release
32−
3331```yaml
3432- uses: typeable/comparest
3533 with:
docs/Releasing.mdnew file+15
@@ -0,0 +1,15 @@
1+# The release procedure
2+
3+1. Update the version number everywhere:
4+ 1. Version in [compaREST.cabal](../compaREST.cabal)
5+ 2. Docker tag in [action.yaml](../action.yaml)
6+ 3. Set the version of unreleased changes in [CHANGELOG.md](../CHANGELOG.md)
7+2. Publish!
8+ 1. Create a pre-release on GitHub:
9+ ![](img/pre-release.png)
10+ 2. This will automatically start the release procedure. You can check the state of it in the "Actions" tab on GitHub:
11+ ![](img/pre-release-ci.png)
12+ 3. If everything goes as planned, the release action should pass and all assets should be added to the release:
13+ ![](img/pre-release-artifacts.png)
14+ 4. The new version should be automatically uploaded a release candidate to [Hackage](https://hackage.haskell.org/package/compaREST/candidates/). Make sure everything looks okay and publish it.
15+ 5. Go to the Releases tab on GitHub and edit the release to make it not a "pre-release".
docs/img/pre-release-artifacts.pngnew file

Binary file not shown.

docs/img/pre-release-ci.pngnew file

Binary file not shown.

docs/img/pre-release.pngnew file

Binary file not shown.

github-action/CompaREST/GitHub/API.hs+2−2
@@ -29,7 +29,7 @@ postStatusProcessing = do
2929 repoOwner
3030 repoName
3131 Check
32− { checkName = mkName Proxy $ "compaREST – " <> projectName
32+ { checkName = mkName Proxy checkName
3333 , checkSha = sha
3434 , checkDetailsURL = Nothing
3535 , checkExternalId = Nothing
@@ -60,7 +60,7 @@ postStatus x = do
6060 repoOwner
6161 repoName
6262 Check
63− { checkName = mkName Proxy $ "compaREST – " <> projectName
63+ { checkName = mkName Proxy checkName
6464 , checkSha = sha
6565 , checkDetailsURL = Nothing
6666 , checkExternalId = Nothing
github-action/CompaREST/GitHub/Action/Config.hs+7−3
@@ -8,13 +8,14 @@ import Data.Text (Text)
88import qualified Data.Text as T
99import qualified GitHub as GH
1010
11+import Data.Functor
1112import System.Envy
1213
1314data Config = Config
1415 { githubToken :: GH.Auth
1516 , repoOwner :: GH.Name GH.Owner
1617 , repoName :: GH.Name GH.Repo
17− , projectName :: Text
18+ , checkName :: Text
1819 , footerText :: Text
1920 , root :: FilePath
2021 , sha :: GH.Name GH.Commit
@@ -27,7 +28,10 @@ instance FromEnv Config where
2728 T.split (== '/') <$> env "REPO" >>= \case
2829 [owner, name] -> pure (owner, name)
2930 _ -> fail "malformed repo"
30− projectName <- env "PROJECT_NAME"
31+ checkName <-
32+ envMaybe "PROJECT_NAME" <&> \case
33+ Just pName | not . T.null . T.strip $ pName -> "compaREST – " <> pName
34+ _ -> "compaREST"
3135 footerText <- env "FOOTER"
3236 root <- envMaybe "ROOT" .!= "."
3337 sha <- env "SHA"
@@ -36,7 +40,7 @@ instance FromEnv Config where
3640 { githubToken = token
3741 , repoOwner = GH.mkName Proxy owner
3842 , repoName = GH.mkName Proxy repo
39− , projectName = projectName
43+ , checkName = checkName
4044 , footerText = footerText
4145 , root = root
4246 , sha = GH.mkName Proxy sha
github-action/Main.hs+13−7
@@ -9,6 +9,7 @@ import Control.Monad.Freer.GitHub
99import Control.Monad.Freer.Reader
1010import Data.OpenApi.Compare.Run
1111import Data.Text (Text)
12+import qualified Data.Text as T
1213import qualified Data.Yaml.Aeson as Yaml
1314import qualified GitHub as GH
1415import System.Environment
@@ -22,13 +23,18 @@ import Text.Pandoc.Writers
2223main :: IO ()
2324main = do
2425 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."
26+ case sha cfg of
27+ x | T.null . T.strip . GH.untagName $ x -> do
28+ putStrLn "SHA not specified. Exiting without doing anything."
29+ pure ()
30+ _ ->
31+ getArgs >>= \case
32+ ["pre"] -> runPre cfg
33+ ["run"] -> do
34+ oldFile <- getEnv "OLD"
35+ newFile <- getEnv "NEW"
36+ runRun cfg (root cfg </> oldFile) (root cfg </> newFile)
37+ _ -> error "Invalid arguments."
3238
3339runner :: Config -> Eff '[GitHub, Error GH.Error, Reader Config, IO] a -> IO a
3440runner cfg =
nix/ci.nix+6−1
@@ -1,6 +1,11 @@
11{ sources ? import ./sources.nix
22, pkgs ? import sources.nixpkgs { }
33}:
4+
5+let
6+ yq = import ./yq.nix { inherit pkgs; };
7+ diff-yaml = import ./diff-yaml.nix { inherit pkgs; };
8+in
49pkgs.mkShell {
5− packages = [ pkgs.haskellPackages.fourmolu ];
10+ packages = [ pkgs.haskellPackages.fourmolu diff-yaml yq ];
611}
nix/diff-yaml.nixnew file+20
@@ -0,0 +1,20 @@
1+{ pkgs ? import <nixpkgs> {} }:
2+
3+let
4+ inherit (pkgs) stdenv;
5+ yq = import ./yq.nix { inherit pkgs; };
6+in
7+stdenv.mkDerivation {
8+ name = "diff-yaml";
9+
10+ src = ../scripts/diff-yaml;
11+
12+ buildInputs = [ pkgs.bash yq ];
13+
14+ phases = [ "installPhase" ];
15+
16+ installPhase = ''
17+ mkdir -p $out/bin
18+ cp $src $out/bin/diff-yaml
19+ '';
20+}
nix/shell.nixnew file+11
@@ -0,0 +1,11 @@
1+{ sources ? import ./sources.nix
2+, pkgs ? import sources.nixpkgs { }
3+}:
4+
5+let
6+ patched-yq = import ./yq.nix { inherit pkgs; };
7+ diffYaml = import ./diff-yaml.nix { inherit pkgs; };
8+in
9+pkgs.mkShell {
10+ buildInputs = with pkgs; [ pkg-config diffYaml patched-yq zlib ];
11+}
nix/sources.json+13−13
@@ -5,10 +5,10 @@
55 "homepage": "https://input-output-hk.github.io/haskell.nix",
66 "owner": "input-output-hk",
77 "repo": "haskell.nix",
8− "rev": "c8a8f57de3c14fcf1c5cda045ed885fb6d06a00e",
9− "sha256": "0frzmvz8rhb4vgx20qms9xxqbkmg5pwivis4qis63ny5wxclzb34",
8+ "rev": "d7f95903c84a5a50d6fe0844280da82c3c81680a",
9+ "sha256": "02870rk4sx44lwwa0k20iwdhqcx0ayz34yb3b0qy045p22q45815",
1010 "type": "tarball",
11− "url": "https://github.com/input-output-hk/haskell.nix/archive/c8a8f57de3c14fcf1c5cda045ed885fb6d06a00e.tar.gz",
11+ "url": "https://github.com/input-output-hk/haskell.nix/archive/d7f95903c84a5a50d6fe0844280da82c3c81680a.tar.gz",
1212 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
1313 },
1414 "niv": {
@@ -17,22 +17,22 @@
1717 "homepage": "https://github.com/nmattia/niv",
1818 "owner": "nmattia",
1919 "repo": "niv",
20− "rev": "5830a4dd348d77e39a0f3c4c762ff2663b602d4c",
21− "sha256": "1d3lsrqvci4qz2hwjrcnd8h5vfkg8aypq3sjd4g3izbc8frwz5sm",
20+ "rev": "f7c538837892dd2eb83567c9f380a11efb59b53f",
21+ "sha256": "0xl33k24vfc29cg9lnp95kvcq69qbq5fzb7jk9ig4lgrhaarh651",
2222 "type": "tarball",
23− "url": "https://github.com/nmattia/niv/archive/5830a4dd348d77e39a0f3c4c762ff2663b602d4c.tar.gz",
23+ "url": "https://github.com/nmattia/niv/archive/f7c538837892dd2eb83567c9f380a11efb59b53f.tar.gz",
2424 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
2525 },
2626 "nix-filter": {
27− "branch": "master",
27+ "branch": "main",
2828 "description": "a small self-container source filtering lib",
2929 "homepage": "",
3030 "owner": "numtide",
3131 "repo": "nix-filter",
32− "rev": "3c9e33ed627e009428197b07216613206f06ed80",
33− "sha256": "19w142crrkywxynmyw4rhz4nglrg64yjawfkw3j91qwkwbfjds84",
32+ "rev": "3342559a24e85fc164b295c3444e8a139924675b",
33+ "sha256": "08xia32g5jzaiyhfl3kzxslzbr4w1i56i0mhy5gpfggv8gqy8sym",
3434 "type": "tarball",
35− "url": "https://github.com/numtide/nix-filter/archive/3c9e33ed627e009428197b07216613206f06ed80.tar.gz",
35+ "url": "https://github.com/numtide/nix-filter/archive/3342559a24e85fc164b295c3444e8a139924675b.tar.gz",
3636 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
3737 },
3838 "nixpkgs": {
@@ -41,10 +41,10 @@
4141 "homepage": "",
4242 "owner": "NixOS",
4343 "repo": "nixpkgs",
44− "rev": "f0fe5e9ba08e658474476317717aca7acacef4d4",
45− "sha256": "1n5syynwmg3x8lj2kyb2ilbxw5nmw7f2kw6yxsi8qadx91hrvkwm",
44+ "rev": "bf038bd724982de67a2ee9a6b2f1fcb7d6a87bd7",
45+ "sha256": "0ivvwdmg9fipj6ylq5ipxqpb0xnz6hsx3lyn377jacfpiqi9w0cv",
4646 "type": "tarball",
47− "url": "https://github.com/NixOS/nixpkgs/archive/f0fe5e9ba08e658474476317717aca7acacef4d4.tar.gz",
47+ "url": "https://github.com/NixOS/nixpkgs/archive/bf038bd724982de67a2ee9a6b2f1fcb7d6a87bd7.tar.gz",
4848 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
4949 }
5050}
nix/yq.nixnew file+26
@@ -0,0 +1,26 @@
1+{ pkgs ? import <nixpkgs> {} }:
2+
3+# Make yq work inside whatever `stack test` launches.
4+# Nuke this when https://github.com/kislyuk/yq/pull/193 is merged
5+# and nixpkgs includes that fix.
6+
7+let
8+ yq-patch = pkgs.writeText "yq-tty-fix.patch" ''
9+diff --git a/yq/__init__.py b/yq/__init__.py
10+index d95ac4b..ae7d59e 100644
11+--- a/yq/__init__.py
12++++ b/yq/__init__.py
13+@@ -110,7 +110,7 @@ def cli(args=None, input_format="yaml", program_name="yq"):
14+ in_place = args.in_place
15+ delattr(args, "in_place")
16+
17+- if sys.stdin.isatty() and not args.input_streams:
18++ if (sys.stdin is None or sys.stdin.isatty()) and not args.input_streams:
19+ parser.print_help()
20+ sys.exit(2)
21+ elif not args.input_streams:
22+ '';
23+in
24+pkgs.yq.overrideAttrs (final: old: {
25+ patches = old.patches ++ [ yq-patch ];
26+})
policeman.jsondeleted−6
@@ -1,6 +0,0 @@
1−[{
2− "file": "compaREST.cabal",
3− "annotation_level": "notice",
4− "end_line": 0,
5− "start_line": 0
6−}]
scripts/diff-yamlnew file+35
@@ -0,0 +1,35 @@
1+#!/usr/bin/env bash
2+set -e
3+
4+# Normalise YAML files passed as inputs, then run diff on them.
5+
6+check_util() {
7+ local util=$1
8+
9+ if [ -z $util ]; then
10+ echo "Error: $util not found in PATH"
11+ exit 1
12+ fi
13+}
14+
15+DIFF=`which diff`
16+check_util $DIFF
17+
18+YQ=`which yq`
19+check_util $YQ
20+
21+# Only normalise YAML files.
22+# Print anything else as is.
23+normalise() {
24+ local file=$1
25+ local contents=($YQ -y -S '.' $file)
26+
27+ # if $contents contains actual YAML data, print it
28+ if [[ $? == 0 ]]; then
29+ echo $contents
30+ else
31+ cat $file
32+ fi
33+}
34+
35+$DIFF -u <(normalise $1) <(normalise $2)
src/Data/OpenApi/Compare/PathsPrefixTree.hs+5−4
@@ -23,7 +23,8 @@ where
2323import Control.Monad
2424import Data.Aeson
2525import Data.Foldable hiding (null, toList)
26−import qualified Data.HashMap.Strict as HM
26+import qualified Data.Aeson.Key as Key
27+import qualified Data.Aeson.KeyMap as KeyMap
2728import Data.Kind
2829import qualified Data.Map as M
2930import Data.Monoid
@@ -256,7 +257,7 @@ newtype MergableObject = MergableObject {getMergableObject :: Object}
256257
257258instance Semigroup MergableObject where
258259 (MergableObject x) <> (MergableObject y) =
259− MergableObject $ HM.unionWith mergeValue x y
260+ MergableObject $ KeyMap.unionWith mergeValue x y
260261 where
261262 mergeValue :: Value -> Value -> Value
262263 mergeValue (Object a) (Object b) =
@@ -271,6 +272,6 @@ instance Monoid MergableObject where
271272
272273traceObject :: Paths q r a -> Value -> Object
273274traceObject Root (Object o) = o
274−traceObject Root v = HM.singleton "root" v
275+traceObject Root v = KeyMap.singleton "root" v
275276traceObject (root `Snoc` s) v =
276− traceObject root . Object $ HM.singleton (T.pack . show $ s) v
277+ traceObject root . Object $ KeyMap.singleton (Key.fromText . T.pack . show $ s) v
src/Data/OpenApi/Compare/Report.hs+1−1
@@ -198,7 +198,7 @@ showErrs x@(P.PathsPrefixNode currentIssues _) =
198198 let -- Extract this pattern if more cases like this arise
199199 ( removedPaths :: Maybe (Orientation, [Issue 'APILevel])
200200 , otherIssues :: Set (AnIssue a)
201− ) = case eqT @a @ 'APILevel of
201+ ) = case eqT @a @'APILevel of
202202 Just Refl
203203 | (S.toList -> p@((AnIssue ori _) : _), o) <-
204204 S.partition
src/Data/OpenApi/Compare/Report/Html/Template.hs+1−1
@@ -36,4 +36,4 @@ template =
3636 \</html>"
3737
3838awsmCss :: ByteString
39−awsmCss = $(makeRelativeToProject "awsm-css/dist/awsm.min.css" >>= embedFile)
39+awsmCss = $(makeRelativeToProject "css/awsm.min.css" >>= embedFile)
src/Data/OpenApi/Compare/Validate/OpenApi.hs+2−1
@@ -38,7 +38,8 @@ tracedSecuritySchemes :: Traced OpenApi -> Traced (Definitions SecurityScheme)
3838tracedSecuritySchemes oa =
3939 traced
4040 (ask oa >>> step ComponentsSecurityScheme)
41− (_componentsSecuritySchemes . _openApiComponents . extract $ oa)
41+ (unSecurityDefinitions . _componentsSecuritySchemes . _openApiComponents . extract $ oa)
42+ where unSecurityDefinitions (SecurityDefinitions ds) = ds
4243
4344tracedResponses :: Traced OpenApi -> Traced (Definitions Response)
4445tracedResponses oa =
src/Data/OpenApi/Compare/Validate/Schema.hs+1−1
@@ -5,7 +5,7 @@ module Data.OpenApi.Compare.Validate.Schema
55 )
66where
77
8−import Control.Monad.Writer
8+import Control.Monad (when,unless)
99import qualified Data.Aeson as A
1010import Data.Coerce
1111import Data.Foldable (for_, toList)
src/Data/OpenApi/Compare/Validate/Schema/JsonFormula.hs+7−6
@@ -18,10 +18,11 @@ where
1818
1919import Algebra.Lattice
2020import qualified Data.Aeson as A
21+import qualified Data.Aeson.Key as Key
22+import qualified Data.Aeson.KeyMap as KeyMap
2123import qualified Data.ByteString.Lazy as BSL
2224import qualified Data.Foldable as F
2325import Data.Functor
24−import qualified Data.HashMap.Strict as HM
2526import Data.Int
2627import Data.Kind
2728import qualified Data.Map as M
@@ -39,7 +40,7 @@ import Data.Text (Text)
3940import qualified Data.Text as T
4041import qualified Data.Text.Encoding as T
4142import Data.Typeable
42−import Text.Pandoc.Builder hiding (Format, Null)
43+import Text.Pandoc.Builder hiding (Format)
4344import Text.Regex.Pcre2
4445
4546data Bound a = Exclusive !a | Inclusive !a
@@ -177,10 +178,10 @@ satisfiesTyped (TArray a) (MaxItems m) = fromIntegral (F.length a) <= m
177178satisfiesTyped (TArray a) (MinItems m) = fromIntegral (F.length a) >= m
178179satisfiesTyped (TArray a) UniqueItems = S.size (S.fromList $ F.toList a) == F.length a -- TODO: could be better #36
179180satisfiesTyped (TObject o) (Properties props additional _) =
180− all (`HM.member` o) (M.keys (M.filter propRequired props))
181− && all (\(k, v) -> satisfies v $ maybe additional propFormula $ M.lookup k props) (HM.toList o)
182−satisfiesTyped (TObject o) (MaxProperties m) = fromIntegral (HM.size o) <= m
183−satisfiesTyped (TObject o) (MinProperties m) = fromIntegral (HM.size o) >= m
181+ all (`KeyMap.member` o) (map Key.fromText $ M.keys (M.filter propRequired props))
182+ && all (\(k, v) -> satisfies v $ maybe additional propFormula $ M.lookup (Key.toText k) props) (KeyMap.toList o)
183+satisfiesTyped (TObject o) (MaxProperties m) = fromIntegral (KeyMap.size o) <= m
184+satisfiesTyped (TObject o) (MinProperties m) = fromIntegral (KeyMap.size o) >= m
184185
185186checkNumberFormat :: Format -> Scientific -> Bool
186187checkNumberFormat "int32" (toRational -> n) =
src/Data/OpenApi/Compare/Validate/Schema/Partition.hs+4−1
@@ -16,6 +16,7 @@ where
1616import Algebra.Lattice
1717import Algebra.Lattice.Lifted
1818import Control.Applicative
19+import Control.Monad (when)
1920import Control.Monad.Reader hiding (ask)
2021import qualified Control.Monad.Reader as R
2122import Control.Monad.State
@@ -31,6 +32,7 @@ import qualified Data.List as L
3132import qualified Data.List.NonEmpty as NE
3233import qualified Data.Map as M
3334import Data.Maybe
35+import Data.Monoid
3436import Data.OpenApi
3537import Data.OpenApi.Compare.Memo
3638import Data.OpenApi.Compare.References
@@ -43,7 +45,8 @@ import Data.Ord
4345import qualified Data.Set as S
4446import Data.Text (Text)
4547import qualified Data.Text as T
46−import Text.Pandoc.Builder hiding (Format, Null)
48+import Data.Traversable (forM)
49+import Text.Pandoc.Builder hiding (Format)
4750
4851data PartitionData
4952 = DByEnumValue (DNF (S.Set A.Value))
src/Data/OpenApi/Compare/Validate/Schema/Process.hs+2
@@ -13,6 +13,7 @@ import Data.Functor.Identity
1313import qualified Data.HashMap.Strict.InsOrd as IOHM
1414import qualified Data.Map as M
1515import Data.Maybe
16+import Data.Monoid
1617import Data.OpenApi hiding (get)
1718import Data.OpenApi.Compare.Behavior
1819import Data.OpenApi.Compare.Memo
@@ -28,6 +29,7 @@ import Data.OpenApi.Compare.Validate.Schema.Traced
2829import Data.OpenApi.Compare.Validate.Schema.TypedJson
2930import Data.Ord
3031import qualified Data.Set as S
32+import Data.Traversable (forM)
3133
3234-- | A fake writer monad that doesn't actually record anything and allows lazy recursion.
3335newtype Silent w a = Silent {runSilent :: a}
stack.yaml+16−4
@@ -1,11 +1,23 @@
1−resolver: lts-18.16
1+resolver: lts-22.34
22
33packages:
44 - .
55
66extra-deps:
7+ - base16-bytestring-1.0.2.0
8+ - freer-simple-1.2.1.2
9+ - generics-sop-0.5.1.4
10+ - github-0.29
11+ - http-link-header-1.2.1
712 - open-union-0.4.0.0
13+ - th-abstraction-0.7.0.0
814 - type-fun-0.1.3
9− - github-0.26
10− - base16-bytestring-0.1.1.7
11− - http-link-header-1.0.3.1
15+ - typerep-map-0.6.0.0
16+
17+# Required for freer-simple and typerep-map
18+allow-newer: true
19+
20+nix:
21+# packages: [pkg-config yq zlib]
22+ shell-file: ./nix/shell.nix
23+ pure: false
stack.yaml.lock+4−4
@@ -41,7 +41,7 @@ packages:
4141 hackage: http-link-header-1.0.3.1
4242snapshots:
4343- completed:
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
44+ size: 587393
45+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/25.yaml
46+ sha256: 1b74fb5e970497b5aefae56703f1bd44aa648bd1a5ef95c1eb8c29775087e2bf
47+ original: lts-18.25
test/Spec/Golden/Extra.hs+2−2
@@ -59,7 +59,7 @@ goldenInputsTree ::
5959 s ->
6060 (t -> IO BSL.ByteString) ->
6161 IO TestTree
62−goldenInputsTree name filepath golden inp f = do
62+goldenInputsTree name filepath golden inp f =
6363 runTestInputTree golden f <$> getGoldenInputs name filepath inp
6464
6565runTestInputTree ::
@@ -72,7 +72,7 @@ runTestInputTree golden f (TestInputNode name rest) =
7272runTestInputTree golden f (TestInputLeaf name t path) =
7373 goldenVsStringDiff
7474 name
75− (\ref new -> ["diff", "-u", ref, new])
75+ (\ref new -> ["diff-yaml", ref, new])
7676 (path </> golden)
7777 (f t)
7878
test/golden/common/random-example/trace-tree.yaml+4−4
@@ -23,6 +23,10 @@ forwardChanges:
2323backwardChanges:
2424 AtPath "/pets":
2525 InOperation GetMethod:
26+ InParam "limit":
27+ InParamSchema:
28+ OfType Number: MatchingMaximumWeak (ProdCons {producer = Inclusive 30.0,
29+ consumer = Inclusive 20.0})
2630 WithStatusCode 200:
2731 ResponsePayload:
2832 PayloadSchema:
@@ -33,10 +37,6 @@ backwardChanges:
3337 - TypesRestricted [Number]
3438 - AdditionalToProperty
3539 - OfType Number: NoMatchingMultipleOf 1.0
36− InParam "limit":
37− InParamSchema:
38− OfType Number: MatchingMaximumWeak (ProdCons {producer = Inclusive 30.0,
39− consumer = Inclusive 20.0})
4040 InOperation PostMethod:
4141 InRequest:
4242 InPayload: