@@ -21,7 +21,7 @@ Today this is caught by manual review or by users who discover the gap.
2121- Run as a verification gate in CI, like ` gleam format --check ` or grep-gates
2222- Provide clear, actionable error messages identifying exactly which function
2323 is missing or which parameter doesn't match
24- - Work with any Gleam package, not just weft_lustre_ui
24+ - Work with any Gleam package
2525- Stay pure Gleam, cross-target compatible
2626
2727## Non-Goals
@@ -94,6 +94,12 @@ pub type ExportSpec {
9494 )
9595}
9696
97+ /// Errors produced while loading package-interface JSON.
98+ pub type LoadError {
99+ ReadError(path: String, reason: simplifile.FileError)
100+ DecodeError(path: String, reason: json.DecodeError)
101+ }
102+
97103/// A single contract violation.
98104pub type Violation {
99105 /// Function exists in source but not in target.
@@ -132,6 +138,11 @@ pub type Violation {
132138 ModuleNotFound(
133139 module: String,
134140 )
141+ /// Package-interface file could not be loaded.
142+ InterfaceLoadFailure(
143+ path: String,
144+ error: LoadError,
145+ )
135146}
136147
137148/// Verification result.
@@ -145,7 +156,7 @@ pub type ContractResult =
145156/// Load and decode a package interface from a JSON file path.
146157pub fn load_package_interface(
147158 path path: String,
148- ) -> Result(PackageInterface, String )
159+ ) -> Result(PackageInterface, LoadError )
149160
150161/// Verify a list of rules against a package interface.
151162/// Returns Ok(Nil) if all rules pass, or Error with a list
@@ -159,6 +170,12 @@ pub fn verify(
159170pub fn format_violations(
160171 violations violations: List(Violation),
161172) -> String
173+
174+ /// Load interface and verify rules in one call.
175+ pub fn check_result(
176+ interface_path interface_path: String,
177+ rules rules: List(Rule),
178+ ) -> ContractResult
162179```
163180
164181### Rule Constructors
@@ -168,7 +185,7 @@ pub fn format_violations(
168185/// public functions, each gaining the specified prefix parameters.
169186///
170187/// Example: headless badge -> styled badge, where styled adds
171- /// a leading `theme ` parameter.
188+ /// a leading `context ` parameter.
172189pub fn mirror_rule(
173190 source source: String,
174191 target target: String,
@@ -200,13 +217,10 @@ pub fn shared_types(
200217### Convenience
201218
202219``` gleam
203- /// Shorthand for [Labeled("theme")] — the common prefix for
204- /// styled wrappers.
205- pub fn theme_prefix() -> List(ParamSpec)
206-
207- /// Load interface from default path and verify rules in one call.
208- /// Exits with code 1 and prints violations if any are found.
209- /// Intended for use in scripts/check.sh.
220+ /// Print violations for terminal usage.
221+ ///
222+ /// `check` is a convenience wrapper around `check_result`.
223+ /// It does not force a process exit.
210224pub fn check(
211225 interface_path interface_path: String,
212226 rules rules: List(Rule),
@@ -231,6 +245,7 @@ creates a verification entry point:
231245``` gleam
232246// test/contract_test.gleam (or a standalone script)
233247import gleam_contracts
248+ import gleam_contracts/rule
234249
235250pub fn main() {
236251 gleam_contracts.check(
@@ -239,12 +254,12 @@ pub fn main() {
239254 gleam_contracts.mirror_rule(
240255 source: "my_package/headless/badge",
241256 target: "my_package/badge",
242- prefix_params: gleam_contracts.theme_prefix() ,
257+ prefix_params: [rule.Labeled(label: "context")] ,
243258 ),
244259 gleam_contracts.mirror_rule(
245260 source: "my_package/headless/button",
246261 target: "my_package/button",
247- prefix_params: gleam_contracts.theme_prefix() ,
262+ prefix_params: [rule.Labeled(label: "context")] ,
248263 )
249264 |> gleam_contracts.with_exceptions(exceptions: ["button"]),
250265 ],
@@ -282,13 +297,13 @@ pub fn contract_tests() {
282297
283298## MirrorRule Semantics
284299
285- Given ` MirrorRule(source: "a/headless/foo", target: "a/foo", prefix_params: [Labeled("theme ")], exceptions: []) ` :
300+ Given ` MirrorRule(source: "a/headless/foo", target: "a/foo", prefix_params: [Labeled("context ")], exceptions: []) ` :
286301
2873021 . For every public function ` f ` in ` a/headless/foo ` :
288303 - ` a/foo ` must have a public function also named ` f `
289304 - If ` f ` is in ` exceptions ` : only existence is checked, not parameters
290305 - Otherwise: ` a/foo.f ` 's parameter labels must equal
291- ` ["theme "] ++ labels_of(a/headless/foo.f) `
306+ ` ["context "] ++ labels_of(a/headless/foo.f) `
292307
2933082 . Extra functions in the target (not present in source) are allowed.
294309 The target is a superset, not an exact mirror.
@@ -301,15 +316,15 @@ Given `MirrorRule(source: "a/headless/foo", target: "a/foo", prefix_params: [Lab
301316Violations produce messages like:
302317
303318```
304- FAIL: weft_lustre_ui /badge is missing function "badge_variant"
305- from weft_lustre_ui /headless/badge
319+ FAIL: my_package /badge is missing function "badge_variant"
320+ from my_package /headless/badge
306321
307- FAIL: weft_lustre_ui /button.button has parameter mismatch
308- expected: [theme , config, label]
309- actual: [theme , config, child]
322+ FAIL: my_package /button.button has parameter mismatch
323+ expected: [context , config, label]
324+ actual: [context , config, child]
310325
311- FAIL: weft_lustre_ui /toggle is missing type "ToggleConfig"
312- from weft_lustre_ui /headless/toggle
326+ FAIL: my_package /toggle is missing type "ToggleConfig"
327+ from my_package /headless/toggle
313328```
314329
315330## Verification
@@ -336,4 +351,4 @@ bash scripts/check.sh
336351- format_violations: produces readable output
337352- load_package_interface: valid JSON -> Ok(interface)
338353- load_package_interface: invalid path -> Error
339- - Integration: real package-interface.json from weft_lustre_ui
354+ - check_result: load failure -> InterfaceLoadFailure
0 commit comments