git.iko.soy/typeable/compaREST
on master help sign in
.github/actions/common_setup/action.yaml+2−2
since splitsnapshots
unifiedsplit
@@ -30,13 +30,13 @@ runs:
30 fi
30 fi
31
31
32 - name: Install Nix
32 - name: Install Nix
33− uses: cachix/install-nix-action@v16
33+ uses: cachix/install-nix-action@v27
34 with:
34 with:
35 extra_nix_config: |
35 extra_nix_config: |
36 system-features = kvm
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=
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/
38 substituters = https://hydra.iohk.io https://cache.nixos.org/
39− - uses: cachix/cachix-action@v10
39+ - uses: cachix/cachix-action@v15
40 with:
40 with:
41 name: typeable
41 name: typeable
42 authToken: "${{ inputs.authToken }}"
42 authToken: "${{ inputs.authToken }}"
.github/workflows/github-actions-image.yaml+60−81
@@ -9,23 +9,23 @@ on:
9 types: [prereleased]
9 types: [prereleased]
10
10
11jobs:
11jobs:
12− check-formatting:
12+# check-formatting:
13− name: Check Haskell formatting
13+# name: Check Haskell formatting
14− runs-on: ubuntu-latest
14+# runs-on: ubuntu-latest
15− steps:
15+# steps:
16− - uses: actions/checkout@v2
16+# - uses: actions/checkout@v2
17−
17+#
18− - name: Common setup
18+# - name: Common setup
19− uses: ./.github/actions/common_setup
19+# uses: ./.github/actions/common_setup
20− with:
20+# with:
21− authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
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
22
27
23− - name: Run fourmolu
28+ check_versions:
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
29 runs-on: ubuntu-latest
30 steps:
30 steps:
31 - uses: actions/checkout@v2
31 - uses: actions/checkout@v2
@@ -37,25 +37,6 @@ jobs:
37 with:
37 with:
38 authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
38 authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
39
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
40 - name: Check library version
60 if: github.event_name == 'release'
41 if: github.event_name == 'release'
61 run: |
42 run: |
@@ -127,13 +108,6 @@ jobs:
127 name: compaREST Docker Image
108 name: compaREST Docker Image
128 path: compaREST-image.tar.gz
109 path: compaREST-image.tar.gz
129
110
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
111 - name: Login to DockerHub
138 uses: docker/login-action@v1.10.0
112 uses: docker/login-action@v1.10.0
139 with:
113 with:
@@ -185,13 +159,18 @@ jobs:
185 tag: ${{ github.ref }}
159 tag: ${{ github.ref }}
186
160
187 dist:
161 dist:
188− needs: check_versions
162+ needs: [check_versions, Linux]
189 runs-on: ubuntu-latest
163 runs-on: ubuntu-latest
190 steps:
164 steps:
191 - uses: actions/checkout@v2
165 - uses: actions/checkout@v2
192 with:
166 with:
193 submodules: recursive
167 submodules: recursive
194
168
169+ - name: Common setup
170+ uses: ./.github/actions/common_setup
171+ with:
172+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
173+
195 - name: Cache
174 - name: Cache
196 uses: actions/cache@v1
175 uses: actions/cache@v1
197 with:
176 with:
@@ -210,7 +189,7 @@ jobs:
210
189
211 - name: Build dist
190 - name: Build dist
212 run: |
191 run: |
213− stack sdist --pvp-bounds both --test-tarball
192+ stack sdist --pvp-bounds both
214 echo "distPath=$(stack path | awk -F ": " '/dist-dir/ {print $2}')" >> $GITHUB_ENV
193 echo "distPath=$(stack path | awk -F ": " '/dist-dir/ {print $2}')" >> $GITHUB_ENV
215
194
216 - name: Upload compaREST sources artifact
195 - name: Upload compaREST sources artifact
@@ -234,7 +213,7 @@ jobs:
234 env:
213 env:
235 HACKAGE_KEY: ${{ secrets.HACKAGE_KEY }}
214 HACKAGE_KEY: ${{ secrets.HACKAGE_KEY }}
236 run: |
215 run: |
237− stack upload --pvp-bounds both --test-tarball --candidate .
216+ stack upload --pvp-bounds both --candidate .
238
217
239 macOS:
218 macOS:
240 needs: check_versions
219 needs: check_versions
@@ -273,39 +252,39 @@ jobs:
273 file: compaREST-macOS-x86.zip
252 file: compaREST-macOS-x86.zip
274 tag: ${{ github.ref }}
253 tag: ${{ github.ref }}
275
254
276− Linux-Windows:
255+# Linux-Windows:
277− needs: check_versions
256+# needs: check_versions
278− runs-on: ubuntu-latest
257+# runs-on: ubuntu-latest
279− steps:
258+# steps:
280− - uses: actions/checkout@v2
259+# - uses: actions/checkout@v2
281− with:
260+# with:
282− submodules: recursive
261+# submodules: recursive
283−
262+#
284− - name: Common setup
263+# - name: Common setup
285− uses: ./.github/actions/common_setup
264+# uses: ./.github/actions/common_setup
286− with:
265+# with:
287− authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
266+# authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
288−
267+#
289− - name: Build Windows binary
268+# - name: Build Windows binary
290− run: |
269+# run: |
291− cp -LR $(nix-build -A WindowsCompaRESTBin -j auto --no-out-link)/bin compaREST
270+# cp -LR $(nix-build -A WindowsCompaRESTBin -j auto --no-out-link)/bin compaREST
292−
271+#
293− - name: Upload Windows compaREST bundle
272+# - name: Upload Windows compaREST bundle
294− uses: actions/upload-artifact@v2
273+# uses: actions/upload-artifact@v2
295− with:
274+# with:
296− name: compaREST binary – Windows
275+# name: compaREST binary – Windows
297− path: compaREST
276+# path: compaREST
298−
277+#
299− - name: Zip Release Assets
278+# - name: Zip Release Assets
300− if: github.event_name == 'release'
279+# if: github.event_name == 'release'
301− shell: bash
280+# shell: bash
302− run: |
281+# run: |
303− zip -r compaREST-Windows.zip compaREST
282+# zip -r compaREST-Windows.zip compaREST
304−
283+#
305− - name: Upload compaREST binary – Windows to release
284+# - name: Upload compaREST binary – Windows to release
306− if: github.event_name == 'release'
285+# if: github.event_name == 'release'
307− uses: svenstaro/upload-release-action@2.2.1
286+# uses: svenstaro/upload-release-action@2.2.1
308− with:
287+# with:
309− repo_token: ${{ secrets.GITHUB_TOKEN }}
288+# repo_token: ${{ secrets.GITHUB_TOKEN }}
310− file: compaREST-Windows.zip
289+# file: compaREST-Windows.zip
311− tag: ${{ github.ref }}
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 @@
1# Changelog
1# Changelog
2
2
3−`comparest` uses [PVP Versioning][1].
3+`compaREST` uses [PVP Versioning][1].
4
4
5−## 0.0.0.0
5+## 0.1.0.1
6
6
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.
8
12
9[1]: https://pvp.haskell.org
13[1]: https://pvp.haskell.org
LICENSE+1−1
@@ -1,6 +1,6 @@
1MIT License
1MIT License
2
2
3−Copyright (c) 2021 Aleksey Uymanov
3+Copyright (c) 2021 Typeable
4
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
6of this software and associated documentation files (the "Software"), to deal
README.md+40−19
@@ -1,14 +1,33 @@
1−# CompaREST
1+# compaREST
2
2
3−[![Hackage](https://img.shields.io/hackage/v/comparest.svg?logo=haskell)](https://hackage.haskell.org/package/comparest)
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)
6[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
5
8Compatibility checker for OpenAPI
6Compatibility checker for OpenAPI
9−# Quick Start Guide
10
7
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+
11−## Your situation
30+### Your situation
12
31
13You 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.)
32You 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
33
@@ -75,7 +94,7 @@ components:
75 $ref: "#/components/schemas/Pet"
94 $ref: "#/components/schemas/Pet"
76```
95```
77
96
78−## Evolving your product
97+### Evolving your product
79
98
80Enthused over your initial success you hurry to release a new and improved version of your API and mobile app.
99Enthused over your initial success you hurry to release a new and improved version of your API and mobile app.
81
100
@@ -144,7 +163,7 @@ components:
144
163
145Looking 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.
164Looking 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
165
147−## Assessing compatibility automatically
166+### Assessing compatibility automatically
148
167
149Luckily, you have access to compaREST which can programmatically analyze your APIs and determine what, if anything, breaks compatibility and what doesn't.
168Luckily, you have access to compaREST which can programmatically analyze your APIs and determine what, if anything, breaks compatibility and what doesn't.
150
169
@@ -158,15 +177,15 @@ Running this command will output a file `report.md`, containing the compatibilit
158
177
159> # Summary
178> # Summary
160>
179>
161−> | [⚠️ Breaking changes](#breaking-changes) | [🙆 Non-breaking changes](#non-breaking-changes) | 🤷 Unsupported feature changes |
180+> | [❌ Breaking changes](#breaking-changes) | [⚠️ Non-breaking changes](#non-breaking-changes) |
162−> |------------------------------------------|-------------------------------------------------|-------------------------------|
181+> |------------------------------------------|--------------------------------------------------|
163−> | 5 | 6 | 0 |
182+> | 5 | 6 |
164>
183>
165−> # <span id="breaking-changes"></span>⚠️ Breaking changes
184+> # <span id="breaking-changes"></span>❌ Breaking changes
166>
185>
167> ## **GET** /pets
186> ## **GET** /pets
168>
187>
169−> ### 📱⬅️ JSON Response – 200
188+> ### ⬅️☁️ JSON Response – 200
170>
189>
171> #### `$[*].name(String)`
190> #### `$[*].name(String)`
172>
191>
@@ -176,7 +195,7 @@ Running this command will output a file `report.md`, containing the compatibilit
176>
195>
177> ## **POST** /pets
196> ## **POST** /pets
178>
197>
179−> ### 📱➡️ JSON Request
198+> ### ➡️☁️ JSON Request
180>
199>
181> #### `$.weight`
200> #### `$.weight`
182>
201>
@@ -191,7 +210,7 @@ Running this command will output a file `report.md`, containing the compatibilit
191>
210>
192> Value is now a multiple of 1.0.
211> Value is now a multiple of 1.0.
193>
212>
194−> # <span id="non-breaking-changes"></span>🙆 Non-breaking changes
213+> # <span id="non-breaking-changes"></span>⚠️ Non-breaking changes
195>
214>
196> ## **GET** /pets
215> ## **GET** /pets
197>
216>
@@ -203,7 +222,7 @@ Running this command will output a file `report.md`, containing the compatibilit
203>
222>
204> Upper bound changed from 20.0 inclusive to 30.0 inclusive.
223> Upper bound changed from 20.0 inclusive to 30.0 inclusive.
205>
224>
206−> ### 📱⬅️ JSON Response – 200
225+> ### ⬅️☁️ JSON Response – 200
207>
226>
208> #### `$[*].weight`
227> #### `$[*].weight`
209>
228>
@@ -220,7 +239,7 @@ Running this command will output a file `report.md`, containing the compatibilit
220>
239>
221> ## **POST** /pets
240> ## **POST** /pets
222>
241>
223−> ### 📱➡️ JSON Request
242+> ### ➡️☁️ JSON Request
224>
243>
225> #### `$.name(String)`
244> #### `$.name(String)`
226>
245>
@@ -230,7 +249,7 @@ Running this command will output a file `report.md`, containing the compatibilit
230
249
231You 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.
250You 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
251
233−## Additional formats
252+### Additional formats
234
253
235You 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:
254You 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
255
@@ -238,7 +257,9 @@ You can also produce a self-contained HTML report that you can open in your brow
238docker 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
257docker 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```
258```
240
259
241−# CLI docs
260+## CLI docs
261+
262+For more detail please see our [user guide](docs/User_guide.md).
242
263
243```
264```
244Usage: comparest (-c|--client ARG) (-s|--server ARG)
265Usage: comparest (-c|--client ARG) (-s|--server ARG)
action.yaml+9−8
@@ -7,21 +7,22 @@ inputs:
7 default: "${{ github.token }}"
7 default: "${{ github.token }}"
8 required: false
8 required: false
9 repo:
9 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.
11 default: "${{ github.repository }}"
11 default: "${{ github.repository }}"
12 required: false
12 required: false
13 project_name:
13 project_name:
14− description: The name of the project to which the API pertains.
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: true
15+ required: false
16+ default: ""
16 footer:
17 footer:
17− description: A footer that can be appended to the comment.
18+ description: A footer that can be appended to the check body.
18 required: false
19 required: false
19 default: ""
20 default: ""
20 old:
21 old:
21− description: The path to old specification of the API.
22+ description: The path to the old (client) specification of the API.
22 required: true
23 required: true
23 new:
24 new:
24− description: The path to new specification of the API.
25+ description: The path to new (server) specification of the API.
25 required: true
26 required: true
26 sha:
27 sha:
27 description: The sha of the commit to post the check for.
28 description: The sha of the commit to post the check for.
@@ -29,7 +30,7 @@ inputs:
29 default: "${{ github.event.pull_request.head.sha }}"
30 default: "${{ github.event.pull_request.head.sha }}"
30runs:
31runs:
31 using: "docker"
32 using: "docker"
32− image: "docker://typeable/comparest-github-action:latest"
33+ image: "docker://typeable/comparest-github-action:0.1.0.1"
33 env:
34 env:
34 GITHUB_TOKEN: "${{ inputs.GITHUB_TOKEN }}"
35 GITHUB_TOKEN: "${{ inputs.GITHUB_TOKEN }}"
35 REPO: "${{ inputs.repo }}"
36 REPO: "${{ inputs.repo }}"
@@ -44,4 +45,4 @@ runs:
44
45
45branding:
46branding:
46 icon: crosshair
47 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
9import GHC.Generics (Generic)
9import GHC.Generics (Generic)
10import Options.Applicative
10import Options.Applicative
11import Options.Applicative.Help hiding (fullDesc)
11import Options.Applicative.Help hiding (fullDesc)
12+import Text.PrettyPrint.ANSI.Leijen ((<$$>),string)
12
13
13parseOptions :: IO Options
14parseOptions :: IO Options
14parseOptions = customExecParser (prefs $ showHelpOnError) optionsParserInfo
15parseOptions = customExecParser (prefs $ showHelpOnError) optionsParserInfo
app/Main.hs+1
@@ -2,6 +2,7 @@ module Main (main) where
2
2
3import Control.Monad
3import Control.Monad
4import Control.Monad.Except
4import Control.Monad.Except
5+import Control.Monad.Trans (lift)
5import Data.Aeson
6import Data.Aeson
6import qualified Data.ByteString.Lazy as BSL
7import qualified Data.ByteString.Lazy as BSL
7import Data.Default
8import Data.Default
awsm-cssdeleted−1

Submodule changed.

compaREST.cabal+6−4
@@ -1,6 +1,6 @@
1cabal-version: 2.4
1cabal-version: 2.4
2name: compaREST
2name: compaREST
3−version: 0.0.0.0
3+version: 0.1.0.1
4synopsis: Compatibility checker for OpenAPI
4synopsis: Compatibility checker for OpenAPI
5description: Compatibility checker for OpenAPI.
5description: Compatibility checker for OpenAPI.
6license: MIT
6license: MIT
@@ -12,8 +12,8 @@ category: Web
12build-type: Simple
12build-type: Simple
13extra-doc-files: README.md
13extra-doc-files: README.md
14 CHANGELOG.md
14 CHANGELOG.md
15−tested-with: GHC == 8.10.4
15+tested-with: GHC == 9.6
16−extra-source-files: awsm-css/dist/awsm.min.css
16+extra-source-files: css/awsm.min.css
17extra-source-files:
17extra-source-files:
18 test/golden/**/*.yaml
18 test/golden/**/*.yaml
19 test/golden/**/*.md
19 test/golden/**/*.md
@@ -26,13 +26,14 @@ common common-options
26 -Wno-safe
26 -Wno-safe
27 -Wno-prepositive-qualified-module
27 -Wno-prepositive-qualified-module
28 -Wno-missing-import-lists
28 -Wno-missing-import-lists
29+ -Wno-missing-kind-signatures
29 -Wno-partial-fields
30 -Wno-partial-fields
30 -Wno-all-missed-specialisations
31 -Wno-all-missed-specialisations
31 -Wno-missing-local-signatures
32 -Wno-missing-local-signatures
32 -Wno-unsafe
33 -Wno-unsafe
33 -fconstraint-solver-iterations=0
34 -fconstraint-solver-iterations=0
34 default-language: Haskell2010
35 default-language: Haskell2010
35− build-depends: base >= 4.12.0.0 && < 4.16
36+ build-depends: base >= 4.12.0.0 && < 5
36 , text
37 , text
37
38
38
39
@@ -165,6 +166,7 @@ executable compaREST
165 , containers
166 , containers
166 , doctemplates
167 , doctemplates
167 , pandoc-types
168 , pandoc-types
169+ , prettyprinter-compat-ansi-wl-pprint
168 ghc-options: -threaded
170 ghc-options: -threaded
169 -rtsopts
171 -rtsopts
170 -with-rtsopts=-N
172 -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 @@
1{ sources ? import ./nix/sources.nix
1{ sources ? import ./nix/sources.nix
2, haskellNix ? import sources.haskellNix { inherit system; }
2, 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; })
4, system ? builtins.currentSystem
4, system ? builtins.currentSystem
5, nix-filter ? import sources.nix-filter
5, nix-filter ? import sources.nix-filter
6}:
6}:
7let
7let
8 masterPkgs = import sources.nixpkgs { inherit system; };
8 masterPkgs = import sources.nixpkgs { inherit system; };
9+ yq = import ./nix/yq.nix { pkgs = masterPkgs; };
10+ diffYaml = import ./nix/diff-yaml.nix { pkgs = masterPkgs; };
9
11
10 hsPkgs = pkgs.haskell-nix.stackProject {
12 hsPkgs = pkgs.haskell-nix.stackProject {
11 src = nix-filter {
13 src = nix-filter {
@@ -38,7 +40,7 @@ let
38 (inDirectory ./src)
40 (inDirectory ./src)
39 (inDirectory ./app)
41 (inDirectory ./app)
40 (inDirectory ./github-action)
42 (inDirectory ./github-action)
41− ./awsm-css/dist/awsm.min.css
43+ ./css/awsm.min.css
42 ./LICENSE
44 ./LICENSE
43 ];
45 ];
44 };
46 };
@@ -97,6 +99,18 @@ let
97 };
99 };
98
100
99 WindowsCompaRESTBin = hsPkgs.projectCross.mingwW64.hsPkgs.compaREST.components.exes.compaREST;
101 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+ });
100in
114in
101{
115{
102 inherit
116 inherit
@@ -106,9 +120,6 @@ in
106 compaRESTBin
120 compaRESTBin
107 hsPkgs
121 hsPkgs
108 macOSCompaRESTBundle
122 macOSCompaRESTBundle
109− WindowsCompaRESTBin;
123+ WindowsCompaRESTBin
110−
124+ test;
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}
125}
docs/Integration_guide.md−2
@@ -28,8 +28,6 @@ In our example we have already generated the two OpenAPI specs and placed them i
28
28
29We can now run our action:
29We can now run our action:
30
30
31−##### NOTE: will only work after public release
32−
33```yaml
31```yaml
34- uses: typeable/comparest
32- uses: typeable/comparest
35 with:
33 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
29 repoOwner
29 repoOwner
30 repoName
30 repoName
31 Check
31 Check
32− { checkName = mkName Proxy $ "compaREST – " <> projectName
32+ { checkName = mkName Proxy checkName
33 , checkSha = sha
33 , checkSha = sha
34 , checkDetailsURL = Nothing
34 , checkDetailsURL = Nothing
35 , checkExternalId = Nothing
35 , checkExternalId = Nothing
@@ -60,7 +60,7 @@ postStatus x = do
60 repoOwner
60 repoOwner
61 repoName
61 repoName
62 Check
62 Check
63− { checkName = mkName Proxy $ "compaREST – " <> projectName
63+ { checkName = mkName Proxy checkName
64 , checkSha = sha
64 , checkSha = sha
65 , checkDetailsURL = Nothing
65 , checkDetailsURL = Nothing
66 , checkExternalId = Nothing
66 , checkExternalId = Nothing
github-action/CompaREST/GitHub/Action/Config.hs+7−3
@@ -8,13 +8,14 @@ import Data.Text (Text)
8import qualified Data.Text as T
8import qualified Data.Text as T
9import qualified GitHub as GH
9import qualified GitHub as GH
10
10
11+import Data.Functor
11import System.Envy
12import System.Envy
12
13
13data Config = Config
14data Config = Config
14 { githubToken :: GH.Auth
15 { githubToken :: GH.Auth
15 , repoOwner :: GH.Name GH.Owner
16 , repoOwner :: GH.Name GH.Owner
16 , repoName :: GH.Name GH.Repo
17 , repoName :: GH.Name GH.Repo
17− , projectName :: Text
18+ , checkName :: Text
18 , footerText :: Text
19 , footerText :: Text
19 , root :: FilePath
20 , root :: FilePath
20 , sha :: GH.Name GH.Commit
21 , sha :: GH.Name GH.Commit
@@ -27,7 +28,10 @@ instance FromEnv Config where
27 T.split (== '/') <$> env "REPO" >>= \case
28 T.split (== '/') <$> env "REPO" >>= \case
28 [owner, name] -> pure (owner, name)
29 [owner, name] -> pure (owner, name)
29 _ -> fail "malformed repo"
30 _ -> fail "malformed repo"
31+ checkName <-
30− projectName <- env "PROJECT_NAME"
32+ envMaybe "PROJECT_NAME" <&> \case
33+ Just pName | not . T.null . T.strip $ pName -> "compaREST – " <> pName
34+ _ -> "compaREST"
31 footerText <- env "FOOTER"
35 footerText <- env "FOOTER"
32 root <- envMaybe "ROOT" .!= "."
36 root <- envMaybe "ROOT" .!= "."
33 sha <- env "SHA"
37 sha <- env "SHA"
@@ -36,7 +40,7 @@ instance FromEnv Config where
36 { githubToken = token
40 { githubToken = token
37 , repoOwner = GH.mkName Proxy owner
41 , repoOwner = GH.mkName Proxy owner
38 , repoName = GH.mkName Proxy repo
42 , repoName = GH.mkName Proxy repo
39− , projectName = projectName
43+ , checkName = checkName
40 , footerText = footerText
44 , footerText = footerText
41 , root = root
45 , root = root
42 , sha = GH.mkName Proxy sha
46 , sha = GH.mkName Proxy sha
github-action/Main.hs+13−7
@@ -9,6 +9,7 @@ import Control.Monad.Freer.GitHub
9import Control.Monad.Freer.Reader
9import Control.Monad.Freer.Reader
10import Data.OpenApi.Compare.Run
10import Data.OpenApi.Compare.Run
11import Data.Text (Text)
11import Data.Text (Text)
12+import qualified Data.Text as T
12import qualified Data.Yaml.Aeson as Yaml
13import qualified Data.Yaml.Aeson as Yaml
13import qualified GitHub as GH
14import qualified GitHub as GH
14import System.Environment
15import System.Environment
@@ -22,13 +23,18 @@ import Text.Pandoc.Writers
22main :: IO ()
23main :: IO ()
23main = do
24main = do
24 cfg <- decodeEnv >>= either error pure
25 cfg <- decodeEnv >>= either error pure
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+ _ ->
25− getArgs >>= \case
31+ getArgs >>= \case
26− ["pre"] -> runPre cfg
32+ ["pre"] -> runPre cfg
27− ["run"] -> do
33+ ["run"] -> do
28− oldFile <- getEnv "OLD"
34+ oldFile <- getEnv "OLD"
29− newFile <- getEnv "NEW"
35+ newFile <- getEnv "NEW"
30− runRun cfg (root cfg </> oldFile) (root cfg </> newFile)
36+ runRun cfg (root cfg </> oldFile) (root cfg </> newFile)
31− _ -> error "Invalid arguments."
37+ _ -> error "Invalid arguments."
32
38
33runner :: Config -> Eff '[GitHub, Error GH.Error, Reader Config, IO] a -> IO a
39runner :: Config -> Eff '[GitHub, Error GH.Error, Reader Config, IO] a -> IO a
34runner cfg =
40runner cfg =
nix/ci.nix+6−1
@@ -1,6 +1,11 @@
1{ sources ? import ./sources.nix
1{ sources ? import ./sources.nix
2, pkgs ? import sources.nixpkgs { }
2, pkgs ? import sources.nixpkgs { }
3}:
3}:
4+
5+let
6+ yq = import ./yq.nix { inherit pkgs; };
7+ diff-yaml = import ./diff-yaml.nix { inherit pkgs; };
8+in
4pkgs.mkShell {
9pkgs.mkShell {
5− packages = [ pkgs.haskellPackages.fourmolu ];
10+ packages = [ pkgs.haskellPackages.fourmolu diff-yaml yq ];
6}
11}
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 @@
5 "homepage": "https://input-output-hk.github.io/haskell.nix",
5 "homepage": "https://input-output-hk.github.io/haskell.nix",
6 "owner": "input-output-hk",
6 "owner": "input-output-hk",
7 "repo": "haskell.nix",
7 "repo": "haskell.nix",
8− "rev": "c8a8f57de3c14fcf1c5cda045ed885fb6d06a00e",
8+ "rev": "d7f95903c84a5a50d6fe0844280da82c3c81680a",
9− "sha256": "0frzmvz8rhb4vgx20qms9xxqbkmg5pwivis4qis63ny5wxclzb34",
9+ "sha256": "02870rk4sx44lwwa0k20iwdhqcx0ayz34yb3b0qy045p22q45815",
10 "type": "tarball",
10 "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",
12 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
12 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
13 },
13 },
14 "niv": {
14 "niv": {
@@ -17,22 +17,22 @@
17 "homepage": "https://github.com/nmattia/niv",
17 "homepage": "https://github.com/nmattia/niv",
18 "owner": "nmattia",
18 "owner": "nmattia",
19 "repo": "niv",
19 "repo": "niv",
20− "rev": "5830a4dd348d77e39a0f3c4c762ff2663b602d4c",
20+ "rev": "f7c538837892dd2eb83567c9f380a11efb59b53f",
21− "sha256": "1d3lsrqvci4qz2hwjrcnd8h5vfkg8aypq3sjd4g3izbc8frwz5sm",
21+ "sha256": "0xl33k24vfc29cg9lnp95kvcq69qbq5fzb7jk9ig4lgrhaarh651",
22 "type": "tarball",
22 "type": "tarball",
23− "url": "https://github.com/nmattia/niv/archive/5830a4dd348d77e39a0f3c4c762ff2663b602d4c.tar.gz",
23+ "url": "https://github.com/nmattia/niv/archive/f7c538837892dd2eb83567c9f380a11efb59b53f.tar.gz",
24 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
24 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
25 },
25 },
26 "nix-filter": {
26 "nix-filter": {
27− "branch": "master",
27+ "branch": "main",
28 "description": "a small self-container source filtering lib",
28 "description": "a small self-container source filtering lib",
29 "homepage": "",
29 "homepage": "",
30 "owner": "numtide",
30 "owner": "numtide",
31 "repo": "nix-filter",
31 "repo": "nix-filter",
32− "rev": "3c9e33ed627e009428197b07216613206f06ed80",
32+ "rev": "3342559a24e85fc164b295c3444e8a139924675b",
33− "sha256": "19w142crrkywxynmyw4rhz4nglrg64yjawfkw3j91qwkwbfjds84",
33+ "sha256": "08xia32g5jzaiyhfl3kzxslzbr4w1i56i0mhy5gpfggv8gqy8sym",
34 "type": "tarball",
34 "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",
36 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
36 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
37 },
37 },
38 "nixpkgs": {
38 "nixpkgs": {
@@ -41,10 +41,10 @@
41 "homepage": "",
41 "homepage": "",
42 "owner": "NixOS",
42 "owner": "NixOS",
43 "repo": "nixpkgs",
43 "repo": "nixpkgs",
44− "rev": "f0fe5e9ba08e658474476317717aca7acacef4d4",
44+ "rev": "bf038bd724982de67a2ee9a6b2f1fcb7d6a87bd7",
45− "sha256": "1n5syynwmg3x8lj2kyb2ilbxw5nmw7f2kw6yxsi8qadx91hrvkwm",
45+ "sha256": "0ivvwdmg9fipj6ylq5ipxqpb0xnz6hsx3lyn377jacfpiqi9w0cv",
46 "type": "tarball",
46 "type": "tarball",
47− "url": "https://github.com/NixOS/nixpkgs/archive/f0fe5e9ba08e658474476317717aca7acacef4d4.tar.gz",
47+ "url": "https://github.com/NixOS/nixpkgs/archive/bf038bd724982de67a2ee9a6b2f1fcb7d6a87bd7.tar.gz",
48 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
48 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
49 }
49 }
50}
50}
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
23import Control.Monad
23import Control.Monad
24import Data.Aeson
24import Data.Aeson
25import Data.Foldable hiding (null, toList)
25import 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
27import Data.Kind
28import Data.Kind
28import qualified Data.Map as M
29import qualified Data.Map as M
29import Data.Monoid
30import Data.Monoid
@@ -256,7 +257,7 @@ newtype MergableObject = MergableObject {getMergableObject :: Object}
256
257
257instance Semigroup MergableObject where
258instance Semigroup MergableObject where
258 (MergableObject x) <> (MergableObject y) =
259 (MergableObject x) <> (MergableObject y) =
259− MergableObject $ HM.unionWith mergeValue x y
260+ MergableObject $ KeyMap.unionWith mergeValue x y
260 where
261 where
261 mergeValue :: Value -> Value -> Value
262 mergeValue :: Value -> Value -> Value
262 mergeValue (Object a) (Object b) =
263 mergeValue (Object a) (Object b) =
@@ -271,6 +272,6 @@ instance Monoid MergableObject where
271
272
272traceObject :: Paths q r a -> Value -> Object
273traceObject :: Paths q r a -> Value -> Object
273traceObject Root (Object o) = o
274traceObject Root (Object o) = o
274−traceObject Root v = HM.singleton "root" v
275+traceObject Root v = KeyMap.singleton "root" v
275traceObject (root `Snoc` s) v =
276traceObject (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 _) =
198 let -- Extract this pattern if more cases like this arise
198 let -- Extract this pattern if more cases like this arise
199 ( removedPaths :: Maybe (Orientation, [Issue 'APILevel])
199 ( removedPaths :: Maybe (Orientation, [Issue 'APILevel])
200 , otherIssues :: Set (AnIssue a)
200 , otherIssues :: Set (AnIssue a)
201− ) = case eqT @a @ 'APILevel of
201+ ) = case eqT @a @'APILevel of
202 Just Refl
202 Just Refl
203 | (S.toList -> p@((AnIssue ori _) : _), o) <-
203 | (S.toList -> p@((AnIssue ori _) : _), o) <-
204 S.partition
204 S.partition
src/Data/OpenApi/Compare/Report/Html/Template.hs+1−1
@@ -36,4 +36,4 @@ template =
36 \</html>"
36 \</html>"
37
37
38awsmCss :: ByteString
38awsmCss :: 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)
38tracedSecuritySchemes oa =
38tracedSecuritySchemes oa =
39 traced
39 traced
40 (ask oa >>> step ComponentsSecurityScheme)
40 (ask oa >>> step ComponentsSecurityScheme)
41− (_componentsSecuritySchemes . _openApiComponents . extract $ oa)
41+ (unSecurityDefinitions . _componentsSecuritySchemes . _openApiComponents . extract $ oa)
42+ where unSecurityDefinitions (SecurityDefinitions ds) = ds
42
43
43tracedResponses :: Traced OpenApi -> Traced (Definitions Response)
44tracedResponses :: Traced OpenApi -> Traced (Definitions Response)
44tracedResponses oa =
45tracedResponses oa =
src/Data/OpenApi/Compare/Validate/Schema.hs+1−1
@@ -5,7 +5,7 @@ module Data.OpenApi.Compare.Validate.Schema
5 )
5 )
6where
6where
7
7
8−import Control.Monad.Writer
8+import Control.Monad (when,unless)
9import qualified Data.Aeson as A
9import qualified Data.Aeson as A
10import Data.Coerce
10import Data.Coerce
11import Data.Foldable (for_, toList)
11import Data.Foldable (for_, toList)
src/Data/OpenApi/Compare/Validate/Schema/JsonFormula.hs+7−6
@@ -18,10 +18,11 @@ where
18
18
19import Algebra.Lattice
19import Algebra.Lattice
20import qualified Data.Aeson as A
20import qualified Data.Aeson as A
21+import qualified Data.Aeson.Key as Key
22+import qualified Data.Aeson.KeyMap as KeyMap
21import qualified Data.ByteString.Lazy as BSL
23import qualified Data.ByteString.Lazy as BSL
22import qualified Data.Foldable as F
24import qualified Data.Foldable as F
23import Data.Functor
25import Data.Functor
24−import qualified Data.HashMap.Strict as HM
25import Data.Int
26import Data.Int
26import Data.Kind
27import Data.Kind
27import qualified Data.Map as M
28import qualified Data.Map as M
@@ -39,7 +40,7 @@ import Data.Text (Text)
39import qualified Data.Text as T
40import qualified Data.Text as T
40import qualified Data.Text.Encoding as T
41import qualified Data.Text.Encoding as T
41import Data.Typeable
42import Data.Typeable
42−import Text.Pandoc.Builder hiding (Format, Null)
43+import Text.Pandoc.Builder hiding (Format)
43import Text.Regex.Pcre2
44import Text.Regex.Pcre2
44
45
45data Bound a = Exclusive !a | Inclusive !a
46data Bound a = Exclusive !a | Inclusive !a
@@ -177,10 +178,10 @@ satisfiesTyped (TArray a) (MaxItems m) = fromIntegral (F.length a) <= m
177satisfiesTyped (TArray a) (MinItems m) = fromIntegral (F.length a) >= m
178satisfiesTyped (TArray a) (MinItems m) = fromIntegral (F.length a) >= m
178satisfiesTyped (TArray a) UniqueItems = S.size (S.fromList $ F.toList a) == F.length a -- TODO: could be better #36
179satisfiesTyped (TArray a) UniqueItems = S.size (S.fromList $ F.toList a) == F.length a -- TODO: could be better #36
179satisfiesTyped (TObject o) (Properties props additional _) =
180satisfiesTyped (TObject o) (Properties props additional _) =
180− all (`HM.member` o) (M.keys (M.filter propRequired props))
181+ all (`KeyMap.member` o) (map Key.fromText $ M.keys (M.filter propRequired props))
181− && all (\(k, v) -> satisfies v $ maybe additional propFormula $ M.lookup k props) (HM.toList o)
182+ && all (\(k, v) -> satisfies v $ maybe additional propFormula $ M.lookup (Key.toText k) props) (KeyMap.toList o)
182−satisfiesTyped (TObject o) (MaxProperties m) = fromIntegral (HM.size o) <= m
183+satisfiesTyped (TObject o) (MaxProperties m) = fromIntegral (KeyMap.size o) <= m
183−satisfiesTyped (TObject o) (MinProperties m) = fromIntegral (HM.size o) >= m
184+satisfiesTyped (TObject o) (MinProperties m) = fromIntegral (KeyMap.size o) >= m
184
185
185checkNumberFormat :: Format -> Scientific -> Bool
186checkNumberFormat :: Format -> Scientific -> Bool
186checkNumberFormat "int32" (toRational -> n) =
187checkNumberFormat "int32" (toRational -> n) =
src/Data/OpenApi/Compare/Validate/Schema/Partition.hs+4−1
@@ -16,6 +16,7 @@ where
16import Algebra.Lattice
16import Algebra.Lattice
17import Algebra.Lattice.Lifted
17import Algebra.Lattice.Lifted
18import Control.Applicative
18import Control.Applicative
19+import Control.Monad (when)
19import Control.Monad.Reader hiding (ask)
20import Control.Monad.Reader hiding (ask)
20import qualified Control.Monad.Reader as R
21import qualified Control.Monad.Reader as R
21import Control.Monad.State
22import Control.Monad.State
@@ -31,6 +32,7 @@ import qualified Data.List as L
31import qualified Data.List.NonEmpty as NE
32import qualified Data.List.NonEmpty as NE
32import qualified Data.Map as M
33import qualified Data.Map as M
33import Data.Maybe
34import Data.Maybe
35+import Data.Monoid
34import Data.OpenApi
36import Data.OpenApi
35import Data.OpenApi.Compare.Memo
37import Data.OpenApi.Compare.Memo
36import Data.OpenApi.Compare.References
38import Data.OpenApi.Compare.References
@@ -43,7 +45,8 @@ import Data.Ord
43import qualified Data.Set as S
45import qualified Data.Set as S
44import Data.Text (Text)
46import Data.Text (Text)
45import qualified Data.Text as T
47import qualified Data.Text as T
48+import Data.Traversable (forM)
46−import Text.Pandoc.Builder hiding (Format, Null)
49+import Text.Pandoc.Builder hiding (Format)
47
50
48data PartitionData
51data PartitionData
49 = DByEnumValue (DNF (S.Set A.Value))
52 = DByEnumValue (DNF (S.Set A.Value))
src/Data/OpenApi/Compare/Validate/Schema/Process.hs+2
@@ -13,6 +13,7 @@ import Data.Functor.Identity
13import qualified Data.HashMap.Strict.InsOrd as IOHM
13import qualified Data.HashMap.Strict.InsOrd as IOHM
14import qualified Data.Map as M
14import qualified Data.Map as M
15import Data.Maybe
15import Data.Maybe
16+import Data.Monoid
16import Data.OpenApi hiding (get)
17import Data.OpenApi hiding (get)
17import Data.OpenApi.Compare.Behavior
18import Data.OpenApi.Compare.Behavior
18import Data.OpenApi.Compare.Memo
19import Data.OpenApi.Compare.Memo
@@ -28,6 +29,7 @@ import Data.OpenApi.Compare.Validate.Schema.Traced
28import Data.OpenApi.Compare.Validate.Schema.TypedJson
29import Data.OpenApi.Compare.Validate.Schema.TypedJson
29import Data.Ord
30import Data.Ord
30import qualified Data.Set as S
31import qualified Data.Set as S
32+import Data.Traversable (forM)
31
33
32-- | A fake writer monad that doesn't actually record anything and allows lazy recursion.
34-- | A fake writer monad that doesn't actually record anything and allows lazy recursion.
33newtype Silent w a = Silent {runSilent :: a}
35newtype Silent w a = Silent {runSilent :: a}
stack.yaml+16−4
@@ -1,11 +1,23 @@
1−resolver: lts-18.16
1+resolver: lts-22.34
2
2
3packages:
3packages:
4 - .
4 - .
5
5
6extra-deps:
6extra-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
7 - open-union-0.4.0.0
12 - open-union-0.4.0.0
13+ - th-abstraction-0.7.0.0
8 - type-fun-0.1.3
14 - type-fun-0.1.3
9− - github-0.26
15+ - typerep-map-0.6.0.0
10− - base16-bytestring-0.1.1.7
16+
11− - http-link-header-1.0.3.1
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:
41 hackage: http-link-header-1.0.3.1
41 hackage: http-link-header-1.0.3.1
42snapshots:
42snapshots:
43- completed:
43- completed:
44− size: 586286
44+ size: 587393
45− url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/16.yaml
45+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/25.yaml
46− sha256: cdead65fca0323144b346c94286186f4969bf85594d649c49c7557295675d8a5
46+ sha256: 1b74fb5e970497b5aefae56703f1bd44aa648bd1a5ef95c1eb8c29775087e2bf
47− original: lts-18.16
47+ original: lts-18.25
test/Spec/Golden/Extra.hs+2−2
@@ -59,7 +59,7 @@ goldenInputsTree ::
59 s ->
59 s ->
60 (t -> IO BSL.ByteString) ->
60 (t -> IO BSL.ByteString) ->
61 IO TestTree
61 IO TestTree
62−goldenInputsTree name filepath golden inp f = do
62+goldenInputsTree name filepath golden inp f =
63 runTestInputTree golden f <$> getGoldenInputs name filepath inp
63 runTestInputTree golden f <$> getGoldenInputs name filepath inp
64
64
65runTestInputTree ::
65runTestInputTree ::
@@ -72,7 +72,7 @@ runTestInputTree golden f (TestInputNode name rest) =
72runTestInputTree golden f (TestInputLeaf name t path) =
72runTestInputTree golden f (TestInputLeaf name t path) =
73 goldenVsStringDiff
73 goldenVsStringDiff
74 name
74 name
75− (\ref new -> ["diff", "-u", ref, new])
75+ (\ref new -> ["diff-yaml", ref, new])
76 (path </> golden)
76 (path </> golden)
77 (f t)
77 (f t)
78
78
test/golden/common/random-example/trace-tree.yaml+4−4
@@ -23,6 +23,10 @@ forwardChanges:
23backwardChanges:
23backwardChanges:
24 AtPath "/pets":
24 AtPath "/pets":
25 InOperation GetMethod:
25 InOperation GetMethod:
26+ InParam "limit":
27+ InParamSchema:
28+ OfType Number: MatchingMaximumWeak (ProdCons {producer = Inclusive 30.0,
29+ consumer = Inclusive 20.0})
26 WithStatusCode 200:
30 WithStatusCode 200:
27 ResponsePayload:
31 ResponsePayload:
28 PayloadSchema:
32 PayloadSchema:
@@ -33,10 +37,6 @@ backwardChanges:
33 - TypesRestricted [Number]
37 - TypesRestricted [Number]
34 - AdditionalToProperty
38 - AdditionalToProperty
35 - OfType Number: NoMatchingMultipleOf 1.0
39 - OfType Number: NoMatchingMultipleOf 1.0
36− InParam "limit":
37− InParamSchema:
38− OfType Number: MatchingMaximumWeak (ProdCons {producer = Inclusive 30.0,
39− consumer = Inclusive 20.0})
40 InOperation PostMethod:
40 InOperation PostMethod:
41 InRequest:
41 InRequest:
42 InPayload:
42 InPayload: