Add AlkTypeKind::to_bast_str/from_bast_str for BAST kind strings

Step 1 of the BAST pivot. Adds the lowercase-string mapping
("uint32" <-> AlkTypeKind::Uint32) that the BAST parser and
validator dispatch on (D-BAST-002).

- to_bast_str(self) -> &'static str: returns the lowercase BAST
  string for all 19 variants (14 primitives + struct/union/array/
  record/enum). Boolean -> "bool", distinct from the PascalCase
  variant name.
- from_bast_str(s) -> Result<AlkTypeKind, AlkTypeError>: inverse of
  to_bast_str; returns AlkTypeError::Schema for unknown strings.

These are additive inherent methods on the already-re-exported enum.
The existing FromStr impl (parsing the v0.1.0 "AlkType:Uint32"
keyword form) is unchanged and removed in step 8. The two surfaces
are deliberately distinct: from_bast_str rejects "AlkType:Uint32"
and FromStr rejects "uint32".

Verification:
- cargo test --release: 402 tests pass (316 lib + 86 integration);
  6 new unit tests cover both directions, round-trip, and the
  from_bast_str/from_str distinctness.
- cargo clippy --all-targets -- -D warnings: clean.
This commit is contained in:
glm-5.2 committed 2026-08-15 11:29:34 +00:00
1 parent f5f52c61e8
commit 66ab9d7d93
1 file changed
+163
+163
View File
@@ -173,6 +173,69 @@ impl AlkTypeKind {
| AlkTypeKind::Record
)
}
/// The lowercase BAST kind string, e.g. `"uint32"` (D-BAST-002).
///
/// This is the string used in BAST documents (`{ "kind": "uint32" }`)
/// and the value the parser and validator dispatch on. Distinct from
/// [`as_str`](Self::as_str), which returns the v0.1.0 `AlkType:Uint32`
/// keyword form.
pub fn to_bast_str(self) -> &'static str {
match self {
AlkTypeKind::Int8 => "int8",
AlkTypeKind::Int16 => "int16",
AlkTypeKind::Int32 => "int32",
AlkTypeKind::Int64 => "int64",
AlkTypeKind::Uint8 => "uint8",
AlkTypeKind::Uint16 => "uint16",
AlkTypeKind::Uint32 => "uint32",
AlkTypeKind::Uint64 => "uint64",
AlkTypeKind::Float32 => "float32",
AlkTypeKind::Float64 => "float64",
AlkTypeKind::Boolean => "bool",
AlkTypeKind::String => "string",
AlkTypeKind::Bytes => "bytes",
AlkTypeKind::Timestamp => "timestamp",
AlkTypeKind::Enum => "enum",
AlkTypeKind::Struct => "struct",
AlkTypeKind::Union => "union",
AlkTypeKind::Array => "array",
AlkTypeKind::Record => "record",
}
}
/// Parse a lowercase BAST kind string into an `AlkTypeKind`
/// (D-BAST-002). Returns `AlkTypeError::Schema` for unknown strings.
///
/// This is the inverse of [`to_bast_str`](Self::to_bast_str) and is
/// distinct from the v0.1.0 `FromStr` impl, which parses the
/// `AlkType:Uint32` keyword form.
pub fn from_bast_str(s: &str) -> Result<AlkTypeKind, AlkTypeError> {
match s {
"int8" => Ok(AlkTypeKind::Int8),
"int16" => Ok(AlkTypeKind::Int16),
"int32" => Ok(AlkTypeKind::Int32),
"int64" => Ok(AlkTypeKind::Int64),
"uint8" => Ok(AlkTypeKind::Uint8),
"uint16" => Ok(AlkTypeKind::Uint16),
"uint32" => Ok(AlkTypeKind::Uint32),
"uint64" => Ok(AlkTypeKind::Uint64),
"float32" => Ok(AlkTypeKind::Float32),
"float64" => Ok(AlkTypeKind::Float64),
"bool" => Ok(AlkTypeKind::Boolean),
"string" => Ok(AlkTypeKind::String),
"bytes" => Ok(AlkTypeKind::Bytes),
"timestamp" => Ok(AlkTypeKind::Timestamp),
"enum" => Ok(AlkTypeKind::Enum),
"struct" => Ok(AlkTypeKind::Struct),
"union" => Ok(AlkTypeKind::Union),
"array" => Ok(AlkTypeKind::Array),
"record" => Ok(AlkTypeKind::Record),
other => Err(AlkTypeError::Schema(format!(
"unknown BAST kind: {other}"
))),
}
}
}
impl fmt::Display for AlkTypeKind {
@@ -1084,4 +1147,104 @@ mod tests {
let schema = json!({"AlkType:Uint32": true});
assert_eq!(get_alktype_kind_enum(&schema), Some(AlkTypeKind::Uint32));
}
#[test]
fn to_bast_str_covers_all_19_variants() {
let cases: &[(AlkTypeKind, &str)] = &[
(AlkTypeKind::Int8, "int8"),
(AlkTypeKind::Int16, "int16"),
(AlkTypeKind::Int32, "int32"),
(AlkTypeKind::Int64, "int64"),
(AlkTypeKind::Uint8, "uint8"),
(AlkTypeKind::Uint16, "uint16"),
(AlkTypeKind::Uint32, "uint32"),
(AlkTypeKind::Uint64, "uint64"),
(AlkTypeKind::Float32, "float32"),
(AlkTypeKind::Float64, "float64"),
(AlkTypeKind::Boolean, "bool"),
(AlkTypeKind::String, "string"),
(AlkTypeKind::Bytes, "bytes"),
(AlkTypeKind::Timestamp, "timestamp"),
(AlkTypeKind::Enum, "enum"),
(AlkTypeKind::Struct, "struct"),
(AlkTypeKind::Union, "union"),
(AlkTypeKind::Array, "array"),
(AlkTypeKind::Record, "record"),
];
assert_eq!(cases.len(), 19);
for (kind, expected) in cases {
assert_eq!(kind.to_bast_str(), *expected);
}
}
#[test]
fn from_bast_str_round_trips_all_variants() {
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::String,
AlkTypeKind::Bytes,
AlkTypeKind::Timestamp,
AlkTypeKind::Enum,
AlkTypeKind::Struct,
AlkTypeKind::Union,
AlkTypeKind::Array,
AlkTypeKind::Record,
] {
let s = kind.to_bast_str();
let parsed = AlkTypeKind::from_bast_str(s).expect("round-trip {s}");
assert_eq!(parsed, kind, "round-trip {s}");
}
}
#[test]
fn from_bast_str_parses_lowercase_strings() {
assert!(matches!(
AlkTypeKind::from_bast_str("uint32"),
Ok(AlkTypeKind::Uint32)
));
assert!(matches!(
AlkTypeKind::from_bast_str("bool"),
Ok(AlkTypeKind::Boolean)
));
assert!(matches!(
AlkTypeKind::from_bast_str("struct"),
Ok(AlkTypeKind::Struct)
));
assert!(matches!(
AlkTypeKind::from_bast_str("timestamp"),
Ok(AlkTypeKind::Timestamp)
));
}
#[test]
fn from_bast_str_rejects_unknown_strings() {
assert!(AlkTypeKind::from_bast_str("uint128").is_err());
assert!(AlkTypeKind::from_bast_str("Uint32").is_err());
assert!(AlkTypeKind::from_bast_str("AlkType:Uint32").is_err());
assert!(AlkTypeKind::from_bast_str("").is_err());
}
#[test]
fn from_bast_str_distinct_from_fromstr_keyword_form() {
assert!("AlkType:Uint32".parse::<AlkTypeKind>().is_ok());
assert!(AlkTypeKind::from_bast_str("AlkType:Uint32").is_err());
assert!(AlkTypeKind::from_bast_str("uint32").is_ok());
assert!("uint32".parse::<AlkTypeKind>().is_err());
}
#[test]
fn to_bast_str_distinct_from_as_str() {
assert_eq!(AlkTypeKind::Uint32.as_str(), "AlkType:Uint32");
assert_eq!(AlkTypeKind::Uint32.to_bast_str(), "uint32");
}
}