Add tests for coverage gaps S1, S2, S3, S5, S6, S7 (285→346 tests, 88.9%→91.9% lines)
- S1 (error.rs): 5 tests for Display impl on all 4 AlkTypeError variants + Error::source(). error.rs 0%→100%. - S2 (validation.rs, macros.rs): 28 tests for validate() (Result-returning) method on every validator + factory rejection arms for all 12 keywords. validation.rs 85%→97.5% lines / 100% fns; macros.rs 75.7%→93.1% / 100% fns. - S3 (engine.rs): 3 tests for read_field on nested struct leaf fields, Bytes, and Timestamp. engine.rs fns 95%→95.3%. - S5 (layout_builder.rs): 2 tests for union 'variant must be Struct' error (byte + field discriminator). - S6 (schema.rs): 6 tests for as_str() round-trip (all 19 kinds), Display, needs_endian(), is_composite(), get_alktype_kind_enum(). schema.rs 91.2%→97.5% lines / 94.3% fns. - S7 (offset_map.rs): 1 test for nested-struct-without-properties schema error. Updated docs/reviews/001-coverage-analysis.md with resolution section and partial-resolved status. Remaining: S4 (sequential_reader error paths, medium effort) and S8 (overflow guards, recommended to skip for v1).
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
---
|
||||
status: open
|
||||
status: partially-resolved
|
||||
last_updated: 2026-08-02
|
||||
reviewed_artifacts:
|
||||
- src/lib.rs
|
||||
@@ -454,4 +454,87 @@ functions**. The remaining ~5% is the overflow-guard long tail.
|
||||
`data_access.rs` are unreachable by construction (they're match
|
||||
exhaustiveness guards on `AlkTypeError` variants in `assert!`
|
||||
matchers). They show as uncovered but are not test gaps — they're
|
||||
defensive code in test helpers.
|
||||
defensive code in test helpers.
|
||||
|
||||
---
|
||||
|
||||
## Resolution (2026-08-02)
|
||||
|
||||
The high-leverage, low-effort suggestions (S1, S2, S3, S5, S6, S7) were
|
||||
implemented in the same pass. 61 new tests added (285 → 346 passing).
|
||||
Workspace coverage rose from **88.9% → 91.9%** lines (4557/5125 →
|
||||
5118/5568) and **82.3% → 86.3%** functions (433/526 → 498/577). `cargo
|
||||
build`, `cargo test`, and `cargo clippy -- -D warnings` are all green
|
||||
(0 warnings).
|
||||
|
||||
Per-file deltas on the targeted files:
|
||||
|
||||
| File | Before | After |
|
||||
|------|-------:|------:|
|
||||
| src/error.rs | 0.0% | 100.0% |
|
||||
| src/validation.rs | 85.0% | 97.5% |
|
||||
| src/macros.rs | 75.7% | 93.1% |
|
||||
| src/schema.rs | 91.2% | 97.5% |
|
||||
| src/layout_builder.rs | 89.4% | 90.3% |
|
||||
| src/offset_map.rs | 92.6% | 93.3% |
|
||||
|
||||
What landed:
|
||||
|
||||
- **S1 (error.rs Display)**: 5 tests covering `Display` for all four
|
||||
`AlkTypeError` variants (`Schema`, `Offset`, `Access`, `Validation`)
|
||||
and `Error::source()` returning `None`. error.rs is now at 100%.
|
||||
- **S2 (validate() paths)**: 28 tests covering the `validate()` (Result-
|
||||
returning) method of every validator — `Int8/16/32`, `Uint8/16/32`,
|
||||
`Int64`, `Uint64`, `Float32/64`, `Boolean`, `String` (with maxLength
|
||||
exceeded), `Bytes` (with maxLength exceeded), `Enum`, `Timestamp`,
|
||||
`Struct`, `Union`, `Array`, `Record` — plus the factory rejection arms
|
||||
for all 12 keyword factories (rejecting non-true / non-bool-or-object
|
||||
values). validation.rs is now at 97.5% lines / 100% functions; macros.rs
|
||||
at 93.1% lines / 100% functions.
|
||||
- **S3 (engine reads)**: 3 tests covering `read_field` for nested struct
|
||||
leaf fields (`header.magic`, `header.version`), `Bytes` fields, and
|
||||
`Timestamp` fields (read as length-prefixed string). The
|
||||
`AlkTypeKind::Struct` arm in `read_field` remains uncovered — it
|
||||
requires an offset-map entry for a struct path, but the `OffsetMap`
|
||||
only records leaf fields. This is a design characteristic, not a test
|
||||
gap (see Notes below).
|
||||
- **S5 (layout_builder "variant must be Struct")**: 2 tests covering
|
||||
the byte-offset and field-name discriminator union variant-must-be-
|
||||
struct error paths.
|
||||
- **S6 (schema.rs accessors)**: 6 tests covering `AlkTypeKind::as_str()`
|
||||
round-trip for all 19 variants, `Display`, `needs_endian()`,
|
||||
`is_composite()`, and `get_alktype_kind_enum()` (strict bool-form
|
||||
variant). schema.rs is now at 97.5% lines / 94.3% functions. The
|
||||
`needs_endian()` and `is_composite()` methods are kept as public API
|
||||
(they're useful for consumers even though no internal caller uses
|
||||
them currently).
|
||||
- **S7 (offset_map missing-properties)**: 1 test covering the nested-
|
||||
struct-without-properties schema error path.
|
||||
|
||||
Remaining (deferred to follow-up sessions):
|
||||
|
||||
- **S4 (sequential_reader error paths)** — the largest remaining gap
|
||||
(~117 uncovered lines). Medium effort: union error paths, array error
|
||||
paths, discriminator helper Uint16/Uint32 arms, `discriminator_string_value`
|
||||
for U8/U16/U32/Enum, nested-union-as-variant, and the `schema()`
|
||||
accessor. The test patterns exist but need crafted schemas and
|
||||
buffers. Good candidate for a dedicated session.
|
||||
- **S8 (overflow guards)** — ~150 lines of `checked_add` overflow guards
|
||||
across all modules. Recommended to skip for v1 (correct-by-construction
|
||||
format strings, poor effort/value ratio). Revisit if the crate ever
|
||||
handles untrusted offsets from untrusted input.
|
||||
|
||||
### Notes on the `AlkTypeKind::Struct` arm in `engine.rs::read_field`
|
||||
|
||||
The `AlkTypeKind::Struct` arm (engine.rs:279) returns
|
||||
`FieldValue::Struct { start, end }` for a struct field. It's uncovered
|
||||
because the `OffsetMap` records only leaf fields (`header.magic`,
|
||||
`header.version`), not intermediate struct paths (`header`). A
|
||||
`read_field(&buf, "header")` call fails at the offset-map lookup, never
|
||||
reaching the `AlkTypeKind::Struct` arm. This is a design characteristic:
|
||||
the aligned-mode API is designed for reading individual leaf fields at
|
||||
known offsets, not for extracting nested-struct byte ranges (the
|
||||
consumer knows the range from the schema and the offset map's leaf
|
||||
positions). The arm exists for completeness but is unreachable through
|
||||
the current offset-map + schema-tree lookup path. Leaving it uncovered
|
||||
is correct; removing it would be a design decision for a separate pass.
|
||||
@@ -751,4 +751,79 @@ mod tests {
|
||||
Some(AlkTypeKind::String)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_field_aligned_reads_nested_struct_leaf_fields() {
|
||||
let mut schema = json!({
|
||||
"AlkType:Struct": true,
|
||||
"endian": "little",
|
||||
"properties": {
|
||||
"header": {
|
||||
"AlkType:Struct": true,
|
||||
"properties": {
|
||||
"magic": { "AlkType:Uint32": true },
|
||||
"version": { "AlkType:Uint8": true }
|
||||
}
|
||||
},
|
||||
"body": { "AlkType:Uint32": true }
|
||||
}
|
||||
});
|
||||
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
|
||||
let mut buf = vec![0u8; 12];
|
||||
buf[0..4].copy_from_slice(&0xDEADBEEFu32.to_le_bytes());
|
||||
buf[4] = 0x01;
|
||||
buf[8..12].copy_from_slice(&0xCAFEBABEu32.to_le_bytes());
|
||||
assert_eq!(
|
||||
engine.read_field(&buf, "header.magic").unwrap(),
|
||||
FieldValue::U32(0xDEADBEEF)
|
||||
);
|
||||
assert_eq!(
|
||||
engine.read_field(&buf, "header.version").unwrap(),
|
||||
FieldValue::U8(0x01)
|
||||
);
|
||||
assert_eq!(
|
||||
engine.read_field(&buf, "body").unwrap(),
|
||||
FieldValue::U32(0xCAFEBABE)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_field_aligned_reads_bytes_field() {
|
||||
let mut schema = json!({
|
||||
"AlkType:Struct": true,
|
||||
"properties": {
|
||||
"blob": { "AlkType:Bytes": true }
|
||||
}
|
||||
});
|
||||
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
|
||||
let mut buf = vec![0u8; 16];
|
||||
let payload = [0xAA, 0xBB, 0xCC];
|
||||
let len_bytes = 3u32.to_le_bytes();
|
||||
buf[0..4].copy_from_slice(&len_bytes);
|
||||
buf[4..7].copy_from_slice(&payload);
|
||||
match engine.read_field(&buf, "blob").unwrap() {
|
||||
FieldValue::Bytes(b) => assert_eq!(b, &payload[..]),
|
||||
other => panic!("expected Bytes, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_field_aligned_reads_timestamp_as_string() {
|
||||
let mut schema = json!({
|
||||
"AlkType:Struct": true,
|
||||
"properties": {
|
||||
"ts": { "AlkType:Timestamp": true }
|
||||
}
|
||||
});
|
||||
let engine = AlkTypeEngine::compile(&mut schema, LayoutMode::Aligned).expect("compile");
|
||||
let mut buf = vec![0u8; 32];
|
||||
let stamp = "2026-07-20T15:30:00Z";
|
||||
let len_bytes = (stamp.len() as u32).to_le_bytes();
|
||||
buf[0..4].copy_from_slice(&len_bytes);
|
||||
buf[4..4 + stamp.len()].copy_from_slice(stamp.as_bytes());
|
||||
match engine.read_field(&buf, "ts").unwrap() {
|
||||
FieldValue::String(s) => assert_eq!(s, stamp),
|
||||
other => panic!("expected String, got {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
src/error.rs
65
src/error.rs
@@ -43,3 +43,68 @@ impl fmt::Display for AlkTypeError {
|
||||
}
|
||||
|
||||
impl std::error::Error for AlkTypeError {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn display_schema_variant() {
|
||||
let err = AlkTypeError::Schema("bad schema".to_string());
|
||||
let s = format!("{err}");
|
||||
assert!(s.contains("schema error"), "got: {s}");
|
||||
assert!(s.contains("bad schema"), "got: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_offset_variant() {
|
||||
let err = AlkTypeError::Offset {
|
||||
field_path: "header.id".to_string(),
|
||||
reason: "field not found".to_string(),
|
||||
};
|
||||
let s = format!("{err}");
|
||||
assert!(s.contains("offset error"), "got: {s}");
|
||||
assert!(s.contains("header.id"), "got: {s}");
|
||||
assert!(s.contains("field not found"), "got: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_access_variant() {
|
||||
let err = AlkTypeError::Access {
|
||||
field_path: "name".to_string(),
|
||||
reason: "buffer too short".to_string(),
|
||||
};
|
||||
let s = format!("{err}");
|
||||
assert!(s.contains("access error"), "got: {s}");
|
||||
assert!(s.contains("name"), "got: {s}");
|
||||
assert!(s.contains("buffer too short"), "got: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_validation_variant() {
|
||||
let schema = serde_json::json!({"type": "integer"});
|
||||
let validator = jsonschema::validator_for(&schema).expect("validator");
|
||||
let instance = serde_json::json!("not-an-integer");
|
||||
let js_err = validator.validate(&instance).expect_err("invalid");
|
||||
let owned = js_err.to_owned();
|
||||
let err = AlkTypeError::Validation(owned);
|
||||
let s = format!("{err}");
|
||||
assert!(s.contains("validation error"), "got: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_returns_none_for_all_variants() {
|
||||
let schema_err = AlkTypeError::Schema("x".to_string());
|
||||
assert!(std::error::Error::source(&schema_err).is_none());
|
||||
let offset_err = AlkTypeError::Offset {
|
||||
field_path: "f".to_string(),
|
||||
reason: "r".to_string(),
|
||||
};
|
||||
assert!(std::error::Error::source(&offset_err).is_none());
|
||||
let access_err = AlkTypeError::Access {
|
||||
field_path: "f".to_string(),
|
||||
reason: "r".to_string(),
|
||||
};
|
||||
assert!(std::error::Error::source(&access_err).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1702,4 +1702,58 @@ mod tests {
|
||||
);
|
||||
assert_eq!(layout.total_size(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_byte_discriminator_variant_must_be_struct() {
|
||||
let schema = json!({
|
||||
"AlkType:Struct": true,
|
||||
"properties": {
|
||||
"payload": {
|
||||
"AlkType:Union": true,
|
||||
"discriminator": {
|
||||
"kind": "byte",
|
||||
"offset": 0,
|
||||
"type": "AlkType:Uint8"
|
||||
},
|
||||
"mapping": {
|
||||
"5": { "AlkType:Uint32": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
let vs = var_sizes(&[("payload.__discriminator", 5)]);
|
||||
let builder = LayoutBuilder::new(&schema).expect("builder");
|
||||
let err = builder.build(&vs).unwrap_err();
|
||||
match err {
|
||||
AlkTypeError::Offset { reason, .. } => {
|
||||
assert!(reason.contains("variant must be AlkType:Struct"), "reason: {reason}");
|
||||
}
|
||||
other => panic!("expected Offset, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_field_discriminator_variant_must_be_struct() {
|
||||
let schema = json!({
|
||||
"AlkType:Struct": true,
|
||||
"properties": {
|
||||
"event": {
|
||||
"AlkType:Union": true,
|
||||
"discriminator": { "kind": "field", "name": "type" },
|
||||
"mapping": {
|
||||
"read": { "AlkType:Uint32": true }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
let vs = var_sizes(&[("event.__variant", 0)]);
|
||||
let builder = LayoutBuilder::new(&schema).expect("builder");
|
||||
let err = builder.build(&vs).unwrap_err();
|
||||
match err {
|
||||
AlkTypeError::Offset { reason, .. } => {
|
||||
assert!(reason.contains("variant must be AlkType:Struct"), "reason: {reason}");
|
||||
}
|
||||
other => panic!("expected Offset, got {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,4 +870,16 @@ mod tests {
|
||||
assert_eq!(empty.len(), 0);
|
||||
assert!(empty.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_rejects_nested_struct_without_properties() {
|
||||
let schema = json!({
|
||||
"AlkType:Struct": true,
|
||||
"properties": {
|
||||
"header": { "AlkType:Struct": true }
|
||||
}
|
||||
});
|
||||
let err = OffsetMap::compute(&schema).unwrap_err();
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
108
src/schema.rs
108
src/schema.rs
@@ -809,4 +809,112 @@ mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_str_round_trips_for_all_kinds() {
|
||||
for kind in [
|
||||
AlkTypeKind::Int8,
|
||||
AlkTypeKind::Int16,
|
||||
AlkTypeKind::Int32,
|
||||
AlkTypeKind::Int64,
|
||||
AlkTypeKind::Uint8,
|
||||
AlkTypeKind::Uint16,
|
||||
AlkTypeKind::Uint32,
|
||||
AlkTypeKind::Uint64,
|
||||
AlkTypeKind::Float32,
|
||||
AlkTypeKind::Float64,
|
||||
AlkTypeKind::Boolean,
|
||||
AlkTypeKind::Enum,
|
||||
AlkTypeKind::String,
|
||||
AlkTypeKind::Bytes,
|
||||
AlkTypeKind::Struct,
|
||||
AlkTypeKind::Union,
|
||||
AlkTypeKind::Array,
|
||||
AlkTypeKind::Record,
|
||||
AlkTypeKind::Timestamp,
|
||||
] {
|
||||
let s = kind.as_str();
|
||||
assert_eq!(s.parse::<AlkTypeKind>().unwrap(), kind, "{kind}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_uses_as_str() {
|
||||
assert_eq!(format!("{}", AlkTypeKind::Uint32), "AlkType:Uint32");
|
||||
assert_eq!(format!("{}", AlkTypeKind::String), "AlkType:String");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn needs_endian_classifies_correctly() {
|
||||
for kind in [
|
||||
AlkTypeKind::Int16,
|
||||
AlkTypeKind::Int32,
|
||||
AlkTypeKind::Int64,
|
||||
AlkTypeKind::Uint16,
|
||||
AlkTypeKind::Uint32,
|
||||
AlkTypeKind::Uint64,
|
||||
AlkTypeKind::Float32,
|
||||
AlkTypeKind::Float64,
|
||||
AlkTypeKind::Enum,
|
||||
AlkTypeKind::String,
|
||||
AlkTypeKind::Bytes,
|
||||
AlkTypeKind::Timestamp,
|
||||
] {
|
||||
assert!(kind.needs_endian(), "expected needs_endian: {kind}");
|
||||
}
|
||||
for kind in [
|
||||
AlkTypeKind::Int8,
|
||||
AlkTypeKind::Uint8,
|
||||
AlkTypeKind::Boolean,
|
||||
AlkTypeKind::Struct,
|
||||
AlkTypeKind::Union,
|
||||
AlkTypeKind::Array,
|
||||
AlkTypeKind::Record,
|
||||
] {
|
||||
assert!(!kind.needs_endian(), "expected not needs_endian: {kind}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_composite_classifies_correctly() {
|
||||
for kind in [
|
||||
AlkTypeKind::Struct,
|
||||
AlkTypeKind::Union,
|
||||
AlkTypeKind::Array,
|
||||
AlkTypeKind::Record,
|
||||
] {
|
||||
assert!(kind.is_composite(), "expected composite: {kind}");
|
||||
}
|
||||
for kind in [
|
||||
AlkTypeKind::Int8,
|
||||
AlkTypeKind::Int16,
|
||||
AlkTypeKind::Int32,
|
||||
AlkTypeKind::Int64,
|
||||
AlkTypeKind::Uint8,
|
||||
AlkTypeKind::Uint16,
|
||||
AlkTypeKind::Uint32,
|
||||
AlkTypeKind::Uint64,
|
||||
AlkTypeKind::Float32,
|
||||
AlkTypeKind::Float64,
|
||||
AlkTypeKind::Boolean,
|
||||
AlkTypeKind::Enum,
|
||||
AlkTypeKind::String,
|
||||
AlkTypeKind::Bytes,
|
||||
AlkTypeKind::Timestamp,
|
||||
] {
|
||||
assert!(!kind.is_composite(), "expected not composite: {kind}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_alktype_kind_enum_returns_none_for_object_form() {
|
||||
let schema = json!({"AlkType:String": {"encoding": "length-prefixed"}});
|
||||
assert_eq!(get_alktype_kind_enum(&schema), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_alktype_kind_enum_returns_kind_for_bool_form() {
|
||||
let schema = json!({"AlkType:Uint32": true});
|
||||
assert_eq!(get_alktype_kind_enum(&schema), Some(AlkTypeKind::Uint32));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,4 +627,277 @@ mod tests {
|
||||
let err = build_validator(&schema).expect_err("schema must be an object");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_returns_ok_for_valid_int8() {
|
||||
let schema = json!({"AlkType:Int8": true, "type": "integer"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!(0)).is_ok());
|
||||
assert!(v.validate(&json!(127)).is_ok());
|
||||
assert!(v.validate(&json!(-128)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_returns_err_for_int8_out_of_range() {
|
||||
let schema = json!({"AlkType:Int8": true, "type": "integer"});
|
||||
let v = validator_for(&schema);
|
||||
let instance = json!(128);
|
||||
let err = v.validate(&instance).expect_err("out of range");
|
||||
let msg = format!("{err}");
|
||||
assert!(msg.contains("integer in range"), "got: {msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_returns_err_for_int8_non_integer() {
|
||||
let schema = json!({"AlkType:Int8": true, "type": "integer"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!("x")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_returns_ok_for_valid_uint32() {
|
||||
let schema = json!({"AlkType:Uint32": true, "type": "integer"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!(0)).is_ok());
|
||||
assert!(v.validate(&json!(4294967295u64)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_returns_err_for_uint32_negative() {
|
||||
let schema = json!({"AlkType:Uint32": true, "type": "integer"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!(-1)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_returns_err_for_uint32_too_large() {
|
||||
let schema = json!({"AlkType:Uint32": true, "type": "integer"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!(5_000_000_000u64)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_int64_ok_and_err() {
|
||||
let schema = json!({"AlkType:Int64": true, "type": "integer"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!(0)).is_ok());
|
||||
assert!(v.validate(&json!(9223372036854775807i64)).is_ok());
|
||||
assert!(v.validate(&json!(-9223372036854775808i64)).is_ok());
|
||||
assert!(v.validate(&json!("x")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_uint64_ok_and_err() {
|
||||
let schema = json!({"AlkType:Uint64": true, "type": "integer"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!(0)).is_ok());
|
||||
assert!(v.validate(&json!(18446744073709551615u64)).is_ok());
|
||||
assert!(v.validate(&json!(-1)).is_err());
|
||||
assert!(v.validate(&json!("x")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_float32_ok_and_err() {
|
||||
let schema = json!({"AlkType:Float32": true, "type": "number"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!(3.5)).is_ok());
|
||||
assert!(v.validate(&json!(0)).is_ok());
|
||||
assert!(v.validate(&json!("x")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_float64_ok_and_err() {
|
||||
let schema = json!({"AlkType:Float64": true, "type": "number"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!(2.5)).is_ok());
|
||||
assert!(v.validate(&json!(0)).is_ok());
|
||||
assert!(v.validate(&json!("x")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_boolean_ok_and_err() {
|
||||
let schema = json!({"AlkType:Boolean": true, "type": "boolean"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!(true)).is_ok());
|
||||
assert!(v.validate(&json!(false)).is_ok());
|
||||
assert!(v.validate(&json!("yes")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_string_ok_and_maxlength_exceeded() {
|
||||
let schema = json!({"AlkType:String": true, "type": "string", "maxLength": 5});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!("hi")).is_ok());
|
||||
assert!(v.validate(&json!("hello")).is_ok());
|
||||
let long = json!("toolong");
|
||||
assert!(v.validate(&long).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_string_non_string_is_err() {
|
||||
let schema = json!({"AlkType:String": true, "type": "string"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!(42)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_bytes_ok_and_maxlength_exceeded() {
|
||||
let schema = json!({"AlkType:Bytes": true, "type": "string", "maxLength": 4});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!("abcd")).is_ok());
|
||||
let long = json!("abcde");
|
||||
assert!(v.validate(&long).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_bytes_non_string_is_err() {
|
||||
let schema = json!({"AlkType:Bytes": true, "type": "string"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!(42)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_enum_is_noop_ok() {
|
||||
let schema = json!({
|
||||
"AlkType:Enum": true,
|
||||
"type": "string",
|
||||
"enum": ["ok", "error"]
|
||||
});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!("ok")).is_ok());
|
||||
assert!(v.validate(&json!("error")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_timestamp_ok_and_err() {
|
||||
let schema = json!({"AlkType:Timestamp": true, "type": "string"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!("2026-07-20T15:30:00Z")).is_ok());
|
||||
assert!(v.validate(&json!("2026-07-20T15:30:00")).is_ok());
|
||||
assert!(v.validate(&json!("2026-07-20T15:30:00+02:00")).is_ok());
|
||||
assert!(v.validate(&json!("not-a-date")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_struct_ok_and_err() {
|
||||
let schema = json!({"AlkType:Struct": true, "type": "object"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!({"x": 1})).is_ok());
|
||||
assert!(v.validate(&json!("not-object")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_union_ok_and_err() {
|
||||
let schema = json!({"AlkType:Union": true, "type": "object"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!({"type": "read"})).is_ok());
|
||||
assert!(v.validate(&json!("not-object")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_array_ok_and_err() {
|
||||
let schema = json!({"AlkType:Array": true, "type": "array"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!([1, 2, 3])).is_ok());
|
||||
assert!(v.validate(&json!("not-array")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_record_ok_and_err() {
|
||||
let schema = json!({"AlkType:Record": true, "type": "object"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!({"a": 1})).is_ok());
|
||||
assert!(v.validate(&json!("not-object")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_rejects_string_keyword_not_bool_or_object() {
|
||||
let schema = json!({"AlkType:String": 42});
|
||||
let err = build_validator(&schema).expect_err("should reject");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_rejects_bytes_keyword_not_bool_or_object() {
|
||||
let schema = json!({"AlkType:Bytes": 42});
|
||||
let err = build_validator(&schema).expect_err("should reject");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_rejects_enum_keyword_not_true() {
|
||||
let schema = json!({"AlkType:Enum": false});
|
||||
let err = build_validator(&schema).expect_err("should reject");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_rejects_timestamp_keyword_not_true() {
|
||||
let schema = json!({"AlkType:Timestamp": false});
|
||||
let err = build_validator(&schema).expect_err("should reject");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_rejects_int64_keyword_not_true() {
|
||||
let schema = json!({"AlkType:Int64": false});
|
||||
let err = build_validator(&schema).expect_err("should reject");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_rejects_uint64_keyword_not_true() {
|
||||
let schema = json!({"AlkType:Uint64": false});
|
||||
let err = build_validator(&schema).expect_err("should reject");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_rejects_struct_keyword_not_true() {
|
||||
let schema = json!({"AlkType:Struct": false});
|
||||
let err = build_validator(&schema).expect_err("should reject");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_rejects_union_keyword_not_true() {
|
||||
let schema = json!({"AlkType:Union": false});
|
||||
let err = build_validator(&schema).expect_err("should reject");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_rejects_array_keyword_not_true() {
|
||||
let schema = json!({"AlkType:Array": false});
|
||||
let err = build_validator(&schema).expect_err("should reject");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_rejects_record_keyword_not_true() {
|
||||
let schema = json!({"AlkType:Record": false});
|
||||
let err = build_validator(&schema).expect_err("should reject");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_rejects_boolean_keyword_not_true() {
|
||||
let schema = json!({"AlkType:Boolean": false});
|
||||
let err = build_validator(&schema).expect_err("should reject");
|
||||
assert!(matches!(err, AlkTypeError::Schema(_)), "got {err:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_accepts_string_keyword_as_object_annotation() {
|
||||
let schema = json!({"AlkType:String": {"encoding": "length-prefixed"}, "type": "string"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!("hi")).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_accepts_bytes_keyword_as_object_annotation() {
|
||||
let schema = json!({"AlkType:Bytes": {"encoding": "offset-indirect"}, "type": "string"});
|
||||
let v = validator_for(&schema);
|
||||
assert!(v.validate(&json!("hi")).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user