Skip to content

Commit 24bb6e2

Browse files
authored
feat: make CLI commands fail where appropriate (#9)
compat - when there is no full compatibility diff - when there are some differences validate - when JSON value is incompatible with a schema
1 parent 8c36401 commit 24bb6e2

File tree

9 files changed

+115
-49
lines changed

9 files changed

+115
-49
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.psc-ide-port
22
.spago
3+
dist
34
output*
45
result

cli.sh

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
set -e
44

5-
EXEC_ARGS=()
6-
for ARG in "$@"; do
7-
EXEC_ARGS+=("--exec-args" "$ARG")
8-
done
5+
spago bundle-app \
6+
--main Main.CLI \
7+
--platform node \
8+
--to dist/cli.mjs \
9+
1>dist/last-build.log 2>&1
910

10-
spago run --main Main.CLI "${EXEC_ARGS[@]}"
11+
node dist/cli.mjs "$@"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

src/purs/CLI.purs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
module CLI (runProgram, OutputFormat(..), Program, ProgramOutput) where
1+
module CLI
2+
( runProgram
3+
, OutputFormat(..)
4+
, Program
5+
, ProgramOutput
6+
, PureProgram
7+
) where
28

39
import Prelude
410

@@ -16,21 +22,27 @@ type ProgramOutput =
1622

1723
type Program i = i ProgramOutput
1824

25+
type PureProgram i o =
26+
i String \/ { expectedError Boolean, output o }
27+
1928
data OutputFormat = Json | Markdown
2029

2130
runProgram
2231
i o
2332
. Document o
2433
EncodeJson o
2534
OutputFormat
26-
(i String \/ o)
35+
PureProgram i o
2736
i
2837
ProgramOutput
2938
runProgram outputFormat compute = compute >>> case _ of
3039
Left errorMessage →
31-
{ exitCode: 1, stderr: errorMessage <> "\n", stdout: "" }
32-
Right output →
33-
{ exitCode: 0, stderr: "", stdout: renderOutput output <> "\n" }
40+
{ exitCode: 2, stderr: errorMessage <> "\n", stdout: "" }
41+
Right { expectedError, output } →
42+
{ exitCode: if expectedError then 1 else 0
43+
, stderr: ""
44+
, stdout: renderOutput output <> "\n"
45+
}
3446
where
3547
renderOutput o String
3648
renderOutput = case outputFormat of

src/purs/CLI/Compat.purs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ module CLI.Compat
66

77
import Prelude
88

9-
import CLI (OutputFormat)
9+
import CLI (OutputFormat, PureProgram)
1010
import Data.Argonaut.Parser as AP
1111
import Data.Either (Either(..))
1212
import Data.Either.Nested (type (\/))
1313
import Data.Newtype (wrap)
1414
import JsonSchema (JsonSchema)
1515
import JsonSchema.Codec.Parsing as Parsing
16-
import JsonSchema.Compatibility (Compatibility)
16+
import JsonSchema.Compatibility (Compatibility(..))
1717
import JsonSchema.Compatibility as Compatibility
1818
import JsonSchema.Difference as Difference
1919

@@ -28,7 +28,9 @@ type ProgramInput =
2828
, rightSchemaText String
2929
}
3030

31-
compute ProgramInput String \/ Compatibility
31+
type ProgramOutput = Compatibility
32+
33+
compute PureProgram ProgramInput ProgramOutput
3234
compute { leftSchemaText, rightSchemaText } = do
3335
leftSchema ← case parseSchema leftSchemaText of
3436
Left errorMessage →
@@ -44,9 +46,18 @@ compute { leftSchemaText, rightSchemaText } = do
4446
Right schema →
4547
Right schema
4648

47-
Right
48-
$ Compatibility.calculate
49-
$ Difference.calculate leftSchema rightSchema
49+
let
50+
differences = Difference.calculate leftSchema rightSchema
51+
52+
Right case Compatibility.calculate differences of
53+
Backward details →
54+
{ expectedError: true, output: Backward details }
55+
Forward details →
56+
{ expectedError: true, output: Forward details }
57+
Full
58+
{ expectedError: false, output: Full }
59+
None details →
60+
{ expectedError: true, output: None details }
5061
where
5162
parseSchema String String \/ JsonSchema
5263
parseSchema s = do

src/purs/CLI/Diff.purs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ module CLI.Diff
66

77
import Prelude
88

9-
import CLI (OutputFormat)
9+
import CLI (OutputFormat, PureProgram)
1010
import Data.Argonaut.Parser as AP
1111
import Data.Either (Either(..))
1212
import Data.Either.Nested (type (\/))
1313
import Data.Newtype (wrap)
1414
import Data.Set (Set)
15+
import Data.Set as Set
1516
import JsonSchema (JsonSchema)
1617
import JsonSchema.Codec.Parsing as Parsing
1718
import JsonSchema.Difference (Difference)
@@ -28,7 +29,9 @@ type ProgramInput =
2829
, rightSchemaText String
2930
}
3031

31-
compute ProgramInput String \/ Set Difference
32+
type ProgramOutput = Set Difference
33+
34+
compute PureProgram ProgramInput ProgramOutput
3235
compute { leftSchemaText, rightSchemaText } = do
3336
leftSchema ← case parseSchema leftSchemaText of
3437
Left errorMessage →
@@ -44,7 +47,13 @@ compute { leftSchemaText, rightSchemaText } = do
4447
Right schema →
4548
Right schema
4649

47-
Right $ Difference.calculate leftSchema rightSchema
50+
let
51+
differences = Difference.calculate leftSchema rightSchema
52+
53+
Right
54+
{ expectedError: not $ Set.isEmpty differences
55+
, output: differences
56+
}
4857
where
4958
parseSchema String String \/ JsonSchema
5059
parseSchema s = do

src/purs/CLI/Validate.purs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ module CLI.Validate
66

77
import Prelude
88

9-
import CLI (OutputFormat)
9+
import CLI (OutputFormat, PureProgram)
1010
import Data.Argonaut.Parser as AP
1111
import Data.Either (Either(..))
1212
import Data.Either.Nested (type (\/))
1313
import Data.Newtype (wrap)
1414
import Data.Set (Set)
15+
import Data.Set as Set
1516
import JsonSchema (JsonSchema)
1617
import JsonSchema.Codec.Parsing as Parsing
1718
import JsonSchema.Validation (Violation)
@@ -29,7 +30,9 @@ type ProgramInput =
2930
, schemaText String
3031
}
3132

32-
compute ProgramInput String \/ Set Violation
33+
type ProgramOutput = Set Violation
34+
35+
compute PureProgram ProgramInput ProgramOutput
3336
compute { jsonText, schemaText } = do
3437
schema ← case parseSchema schemaText of
3538
Left errorMessage →
@@ -45,7 +48,11 @@ compute { jsonText, schemaText } = do
4548
Right json →
4649
Right json
4750

48-
Right $ jsonValue `Validation.validateAgainst` schema
51+
let
52+
violations = jsonValue `Validation.validateAgainst` schema
53+
54+
Right
55+
{ expectedError: not $ Set.isEmpty violations, output: violations }
4956
where
5057
parseJson String String \/ JsonValue
5158
parseJson = map wrap <<< AP.jsonParser

src/purs/Main/CLI.purs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,6 @@ run { command } = Aff.catchError executeProgram fallbackProgram
109109
executeProgram Aff ProgramOutput
110110
executeProgram = case command of
111111
Compat { leftSchemaPath, outputFormat, rightSchemaPath } → do
112-
Console.error $ "calculating compatibility between schemata at "
113-
<> leftSchemaPath
114-
<> " and "
115-
<> rightSchemaPath
116-
117112
leftSchemaText ← FS.readTextFile UTF8 leftSchemaPath
118113
rightSchemaText ← FS.readTextFile UTF8 rightSchemaPath
119114

@@ -123,11 +118,6 @@ run { command } = Aff.catchError executeProgram fallbackProgram
123118
{ leftSchemaText, rightSchemaText }
124119

125120
Diff { leftSchemaPath, outputFormat, rightSchemaPath } → do
126-
Console.error $ "calculating difference between schemata at "
127-
<> leftSchemaPath
128-
<> " and "
129-
<> rightSchemaPath
130-
131121
leftSchemaText ← FS.readTextFile UTF8 leftSchemaPath
132122
rightSchemaText ← FS.readTextFile UTF8 rightSchemaPath
133123

@@ -137,11 +127,6 @@ run { command } = Aff.catchError executeProgram fallbackProgram
137127
{ leftSchemaText, rightSchemaText }
138128

139129
Validate { jsonPath, outputFormat, schemaPath } → do
140-
Console.error $ "validating JSON at "
141-
<> jsonPath
142-
<> " against schema at "
143-
<> schemaPath
144-
145130
schemaText ← FS.readTextFile UTF8 schemaPath
146131
jsonText ← FS.readTextFile UTF8 jsonPath
147132

test/snapshot/purs/Test/Spec/CLI.purs

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Prelude
44

55
import Control.Monad.Error.Class (throwError)
66
import Data.Foldable (foldMap)
7+
import Data.Maybe (Maybe(..))
78
import Data.String as String
89
import Data.String.NonEmpty (NonEmptyString)
910
import Data.String.NonEmpty as StringNE
@@ -18,6 +19,7 @@ import Type.Proxy (Proxy(..))
1819
type Input =
1920
{ command NonEmptyString
2021
, parameters Array Parameter
22+
, shouldFail Boolean
2123
}
2224

2325
type Parameter = NonEmptyString /\ NonEmptyString
@@ -42,24 +44,35 @@ fixtures =
4244
)
4345
, leftSchemaFile: StringNE.nes (Proxy Proxy "any-number.json")
4446
, rightSchemaFile: StringNE.nes (Proxy Proxy "any-string.json")
47+
, shouldFail: true
4548
}
4649
]
4750

4851
diffFixtures Array (Fixture Input)
4952
diffFixtures = diffFixture <$>
5053
[ { differencesFile: StringNE.nes
54+
( Proxy
55+
Proxy "no-differences.json"
56+
)
57+
, leftSchemaFile: StringNE.nes (Proxy Proxy "any-number.json")
58+
, rightSchemaFile: StringNE.nes (Proxy Proxy "any-number.json")
59+
, shouldFail: false
60+
}
61+
, { differencesFile: StringNE.nes
5162
( Proxy
5263
Proxy "allowed-types-change-from-number-to-string.json"
5364
)
5465
, leftSchemaFile: StringNE.nes (Proxy Proxy "any-number.json")
5566
, rightSchemaFile: StringNE.nes (Proxy Proxy "any-string.json")
67+
, shouldFail: true
5668
}
5769
]
5870

5971
validateFixtures Array (Fixture Input)
6072
validateFixtures = validateFixture <$>
6173
[ { jsonFile: StringNE.nes (Proxy Proxy "string.json")
6274
, schemaFile: StringNE.nes (Proxy Proxy "any-number.json")
75+
, shouldFail: true
6376
, violationsFile: StringNE.nes
6477
(Proxy Proxy "string-is-not-a-number.json")
6578
}
@@ -69,15 +82,18 @@ compatFixture
6982
{ compatibilitiesFile NonEmptyString
7083
, leftSchemaFile NonEmptyString
7184
, rightSchemaFile NonEmptyString
85+
, shouldFail Boolean
7286
}
7387
Fixture Input
74-
compatFixture { compatibilitiesFile, leftSchemaFile, rightSchemaFile } =
88+
compatFixture
89+
{ compatibilitiesFile, leftSchemaFile, rightSchemaFile, shouldFail } =
7590
{ input:
7691
{ command: StringNE.nes (Proxy Proxy "compat")
7792
, parameters:
7893
[ StringNE.nes (Proxy Proxy "left") /\ leftSchemaPath
7994
, StringNE.nes (Proxy Proxy "right") /\ rightSchemaPath
8095
]
96+
, shouldFail
8197
}
8298
, outputPath
8399
}
@@ -100,15 +116,18 @@ diffFixture
100116
{ differencesFile NonEmptyString
101117
, leftSchemaFile NonEmptyString
102118
, rightSchemaFile NonEmptyString
119+
, shouldFail Boolean
103120
}
104121
Fixture Input
105-
diffFixture { differencesFile, leftSchemaFile, rightSchemaFile } =
122+
diffFixture
123+
{ differencesFile, leftSchemaFile, rightSchemaFile, shouldFail } =
106124
{ input:
107125
{ command: StringNE.nes (Proxy Proxy "diff")
108126
, parameters:
109127
[ StringNE.nes (Proxy Proxy "left") /\ leftSchemaPath
110128
, StringNE.nes (Proxy Proxy "right") /\ rightSchemaPath
111129
]
130+
, shouldFail
112131
}
113132
, outputPath
114133
}
@@ -130,16 +149,18 @@ diffFixture { differencesFile, leftSchemaFile, rightSchemaFile } =
130149
validateFixture
131150
{ jsonFile NonEmptyString
132151
, schemaFile NonEmptyString
152+
, shouldFail Boolean
133153
, violationsFile NonEmptyString
134154
}
135155
Fixture Input
136-
validateFixture { jsonFile, schemaFile, violationsFile } =
156+
validateFixture { jsonFile, schemaFile, shouldFail, violationsFile } =
137157
{ input:
138158
{ command: StringNE.nes (Proxy Proxy "validate")
139159
, parameters:
140160
[ StringNE.nes (Proxy Proxy "json") /\ jsonPath
141161
, StringNE.nes (Proxy Proxy "schema") /\ schemaPath
142162
]
163+
, shouldFail
143164
}
144165
, outputPath
145166
}
@@ -167,15 +188,31 @@ describeInput { command, parameters } = "a CLI invocation of '"
167188
StringNE.toString k <> "=" <> StringNE.toString v
168189

169190
executeCommand Input Aff String
170-
executeCommand { command, parameters } = do
191+
executeCommand { command, parameters, shouldFail } = do
171192
result ← runCliProcess `pipe` runPrettierProcess
172-
case result.exit of
193+
let
194+
{ escapedCommand, exit, stderr, stdout } = result
195+
case exit of
173196
BySignal _ →
174-
throwError $ error $ "process killed: " <> result.escapedCommand
175-
Normally 0do
176-
pure $ result.stdout <> "\n"
177-
Normally _ →
178-
pure $ result.stderr <> "\n"
197+
throwError $ error $ "process killed: " <> escapedCommand
198+
Normally 0
199+
if shouldFail then throwError
200+
$ error
201+
$ "command should fail but it did not: "
202+
<> show { stderr, stdout }
203+
else pure $ stdout <> "\n"
204+
Normally 1
205+
if shouldFail then pure $ stdout <> "\n"
206+
else throwError
207+
$ error
208+
$ "command should not fail but it did: "
209+
<> show { exitCode: 1, stderr, stdout }
210+
Normally exitCode →
211+
if shouldFail then pure $ stderr <> "\n"
212+
else throwError
213+
$ error
214+
$ "command should not fail but it did: "
215+
<> show { exitCode, stderr, stdout }
179216
where
180217
runCliProcess Aff ExecaProcess
181218
runCliProcess = E.execa
@@ -203,9 +240,11 @@ pipe runProcess1 runProcess2 = do
203240
process1 ← runProcess1
204241
result1 ← process1.getResult
205242
case result1.exit of
206-
Normally 0do
243+
Normally exitCodedo
207244
process2 ← runProcess2
208245
process2.stdin.writeUtf8End result1.stdout
209-
process2.getResult
246+
result2 ← process2.getResult
247+
pure $ result1
248+
{ exitCode = Just exitCode, stdout = result2.stdout }
210249
_ →
211250
pure result1

0 commit comments

Comments
 (0)