Configuration contract compatibility checks for MoonBit
Dependencies
///|
test {
let previous = @configscope.ConfigValue::object(
Map([("port", @configscope.ConfigValue::number(80.0))]),
)
let next = @configscope.ConfigValue::object(
Map([("port", @configscope.ConfigValue::number(8080.0))]),
)
let report = previous.compatibility_with(next)
inspect(report.breaking_count(), content="0")
inspect(report.behavioral_count(), content="1")
}moon run cmd/main -- compat cmd/main/testdata/basic.json cmd/main/testdata/compatibility-compatible.json
gate: breaking
[behavioral] changed: features
[compatible] added: logging
[behavioral] changed: server.port
summary: compatible=1 behavioral=2 breaking=0
compatibility check passedmoon run cmd/main -- compat-matrix cmd/main/testdata/compatibility-compatible.json cmd/main/testdata/basic.json cmd/main/testdata/compatibility-breaking.json
gate: breaking
baseline: cmd/main/testdata/basic.json
[behavioral] changed: features
[compatible] added: logging
[behavioral] changed: server.port
result: passed
baseline: cmd/main/testdata/compatibility-breaking.json
[compatible] added: features
[compatible] added: logging
[breaking] type_changed: server.port
result: failed (1 gate violation)
summary: passed=1 failed=1 total=2
compatibility matrix failed: 1 baseline exceeds the 'breaking' gate{
"candidate": "configs/v2.json",
"baselines": ["configs/v1.json", "configs/v1.5.json"],
"fail_on": "breaking"
}moon run cmd/main -- contract-check configscope.contract.jsonmoon run cmd/main -- contract-init cmd/main/testdata/compatibility-compatible.json cmd/main/testdata/basic.json --output release.contract.json
moon run cmd/main -- contract-check release.contract.json///|
test {
let path = @configscope.parse_path("server.http.port").unwrap()
inspect(path.length(), content="3")
inspect(path.to_string(), content="server.http.port")
}///|
test {
let server = @configscope.ConfigValue::object(
Map([
("host", @configscope.ConfigValue::string("127.0.0.1")),
("port", @configscope.ConfigValue::number(8080.0)),
]),
)
inspect(server.length().unwrap(), content="2")
assert_eq(server.field("port").unwrap().as_number().unwrap(), 8080.0)
}///|
test {
let config = @configscope.ConfigValue::object(
Map([
(
"server",
@configscope.ConfigValue::object(
Map([("port", @configscope.ConfigValue::number(8080.0))]),
),
),
]),
)
let path = @configscope.parse_path("server.port").unwrap()
inspect(config.get(path).unwrap().as_number().unwrap(), content="8080")
}///|
test {
let defaults = @configscope.ConfigValue::object(
Map([("port", @configscope.ConfigValue::number(80.0))]),
)
let environment = @configscope.ConfigValue::object(
Map([("port", @configscope.ConfigValue::number(8080.0))]),
)
let merged = defaults.merge(environment)
inspect(merged.field("port").unwrap().as_number().unwrap(), content="8080")
}///|
test {
let defaults = @configscope.ConfigValue::object(
Map([
(
"server",
@configscope.ConfigValue::object(
Map([("port", @configscope.ConfigValue::number(80.0))]),
),
),
]),
)
let production = @configscope.ConfigValue::object(
Map([
(
"server",
@configscope.ConfigValue::object(
Map([("host", @configscope.ConfigValue::string("prod"))]),
),
),
]),
)
let merged = defaults.deep_merge(production)
inspect(
merged
.get(@configscope.parse_path("server.port").unwrap())
.unwrap()
.as_number()
.unwrap(),
content="80",
)
inspect(
merged
.get(@configscope.parse_path("server.host").unwrap())
.unwrap()
.as_string()
.unwrap(),
content="prod",
)
}///|
test {
let defaults = @configscope.ConfigValue::object(
Map([
(
"limits",
@configscope.ConfigValue::object(
Map([("requests", @configscope.ConfigValue::number(100.0))]),
),
),
]),
)
let production = @configscope.ConfigValue::object(
Map([("limits", @configscope.ConfigValue::number(10.0))]),
)
let report = defaults.deep_merge_with_report(production)
inspect(report.conflict_count(), content="1")
let conflict = report.conflict(0).unwrap()
inspect(conflict.path().to_string(), content="limits")
inspect(conflict.earlier_kind().to_string(), content="object")
inspect(conflict.later_kind().to_string(), content="number")
}///|
test {
let value = @configscope.ConfigValue::object(
Map([("region", @configscope.ConfigValue::string("eu-west"))]),
)
let production = @legacy.ConfigLayer::new("production", value)
inspect(production.name(), content="production")
inspect(
production.value().field("region").unwrap().as_string().unwrap(),
content="eu-west",
)
}///|
test {
let defaults = @legacy.ConfigLayer::new(
"defaults",
@configscope.ConfigValue::object(
Map([("port", @configscope.ConfigValue::number(80.0))]),
),
)
let production = @legacy.ConfigLayer::new(
"production",
@configscope.ConfigValue::object(
Map([("port", @configscope.ConfigValue::number(8080.0))]),
),
)
let config = @legacy.LayeredConfig::new([defaults, production])
.merge()
.unwrap()
inspect(config.field("port").unwrap().as_number().unwrap(), content="8080")
}///|
test {
let defaults = @legacy.ConfigLayer::new(
"defaults",
@configscope.ConfigValue::object(
Map([("port", @configscope.ConfigValue::number(80.0))]),
),
)
let production = @legacy.ConfigLayer::new(
"production",
@configscope.ConfigValue::object(
Map([("port", @configscope.ConfigValue::number(8080.0))]),
),
)
let result = @legacy.LayeredConfig::new([defaults, production])
.merge_with_provenance()
.unwrap()
inspect(
result.source(@configscope.parse_path("port").unwrap()).unwrap(),
content="production",
)
}///|
test {
let layer = @legacy.ConfigLayer::new(
"production",
@configscope.ConfigValue::object(
Map([("port", @configscope.ConfigValue::number(8080.0))]),
),
)
let result = @legacy.LayeredConfig::new([layer])
.merge_with_provenance()
.unwrap()
let explanation = result
.explain(@configscope.parse_path("port").unwrap())
.unwrap()
inspect(explanation.path().to_string(), content="port")
inspect(explanation.kind().to_string(), content="number")
inspect(explanation.source(), content="production")
}///|
test {
let before = @configscope.ConfigValue::object(
Map([
("host", @configscope.ConfigValue::string("localhost")),
("port", @configscope.ConfigValue::number(80.0)),
]),
)
let after = @configscope.ConfigValue::object(
Map([
("port", @configscope.ConfigValue::number(8080.0)),
("tls", @configscope.ConfigValue::boolean(true)),
]),
)
let differences = before.diff(after)
inspect(differences.length(), content="3")
inspect(differences.get(0).unwrap().path().to_string(), content="host")
inspect(differences.get(0).unwrap().kind().to_string(), content="removed")
}///|
test {
let value = @configscope.ConfigValue::object(
Map([
(
"server",
@configscope.ConfigValue::object(
Map([("host", @configscope.ConfigValue::string("localhost"))]),
),
),
]),
)
let issues = value.audit([
@legacy.ConfigAuditRule::required(
@configscope.parse_path("server.host").unwrap(),
),
@legacy.ConfigAuditRule::required(
@configscope.parse_path("server.port").unwrap(),
),
@legacy.ConfigAuditRule::type_is(
@configscope.parse_path("server.host").unwrap(),
String,
),
])
inspect(issues.length(), content="1")
inspect(issues.get(0).unwrap().path().to_string(), content="server.port")
inspect(issues.get(0).unwrap().kind().to_string(), content="missing_required")
}`get` is no longer exposed by the main CLI.
The main CLI no longer exposes this command.`audit` is no longer exposed by the main CLI.
The legacy API reports whether its rules pass; the primary CLI does not expose it.`merge` is no longer exposed by the main CLI.
`Noverberrain/configscope/legacy`moon run cmd/main -- contract-check configscope.contract.json --format jsonInstall
Download zipConfiguration contract compatibility checks for MoonBit
Dependencies