Viewing: fid.rs
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 DDN. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
use crate::{
Error, LustrePath,
error::{Result, cvt_lz_m, cvt_nz, cvt_rc_m},
};
use lustreapi_sys::{
llapi_fd2fid, llapi_fid2path_at, llapi_open_by_fid_at, llapi_path2fid, lu_fid,
};
use cstrbuf::CStrBuf;
use serde::{Deserialize, Serialize};
use std::{
ffi::CString,
fmt::{self, Display, Formatter},
fs::File,
os::fd::{AsRawFd, FromRawFd},
path::{Path, PathBuf},
ptr,
};
pub const MAX_PATH_LEN: usize = 4096;
/// A Lustre File Identifier (FID) that uniquely identifies files in a Lustre filesystem.
///
/// A FID consists of three components:
/// - `seq`: A 64-bit sequence number identifying the target
/// - `oid`: A 32-bit object ID within that sequence
/// - `ver`: A 32-bit version number
///
/// # Features
///
/// This implementation provides:
/// - Conversion to/from Lustre's native `lu_fid` struct
/// - Serialization and deserialization through Serde
/// - String parsing and formatting with `[0xSEQ:0xOID:0xVER]` syntax
/// - Methods to obtain `FIDs` from file paths or descriptors
/// - Opening files using their FID
///
/// # Examples
///
/// ```
/// use rustreapi::Fid;
///
/// // Create a FID directly
/// let fid = Fid::new(0xCAFE, 0x11, 0x22);
///
/// // Parse from string representation
/// let parsed = Fid::parse("[0xCAFE:0x11:0x22]").unwrap();
/// assert_eq!(fid, parsed);
///
/// // Get string representation
/// assert_eq!(fid.to_string(), "[0xCAFE:0x11:0x22]");
/// ```
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub struct Fid {
seq: u64,
oid: u32,
ver: u32,
}
impl Serialize for Fid {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Fid {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Fid::parse(&s).map_err(serde::de::Error::custom)
}
}
/// Constructors and utility methods for `Fid`.
impl Fid {
/// Creates a new FID with the specified sequence, object ID, and version numbers.
///
/// # Arguments
///
/// * `seq` - The 64-bit sequence number
/// * `oid` - The 32-bit object ID
/// * `ver` - The 32-bit version number
pub fn new(seq: u64, oid: u32, ver: u32) -> Self {
Self { oid, seq, ver }
}
/// Retrieves the FID for a file referenced by an open file descriptor.
///
/// This method obtains the Lustre File Identifier (FID) for a file that has already
/// been opened, using its file descriptor. The file must be on a Lustre filesystem.
///
/// # Arguments
///
/// * `fd` - Any type that implements `AsRawFd`, typically a `File` or file descriptor wrapper
///
/// # Returns
///
/// * `Ok(Fid)` - The FID of the file if successful
/// * `Err` - If the file descriptor is invalid or the file is not on a Lustre filesystem
///
/// # Examples
///
/// ```no_run
/// # use std::error::Error;
/// # use std::fs::File;
/// # fn example() -> Result<(), Box<dyn Error>> {
/// use rustreapi::Fid;
///
/// // Open a file on a Lustre filesystem
/// let file = File::open("/mnt/lustre/myfile.txt")?;
///
/// // Get its FID
/// let fid = Fid::with_fd(&file)?;
/// println!("File FID: {}", fid);
/// # Ok(())
/// # }
/// ```
pub fn with_fd<Fd: AsRawFd>(fd: &Fd) -> Result<Self> {
let mut fid = lu_fid::default();
unsafe { cvt_nz(llapi_fd2fid(fd.as_raw_fd(), &mut fid as *mut lu_fid))? }
Ok(fid.into())
}
/// Retrieves the FID for a file at the specified path.
///
/// This method obtains the Lustre File Identifier (FID) for a file using its path.
/// It internally calls the Lustre API function `llapi_path2fid()`.
///
/// # Arguments
///
/// * `path` - Path to a file on a Lustre filesystem
///
/// # Returns
///
/// * `Ok(Fid)` - The FID of the file if successful
/// * `Err` - If the path is invalid or not on a Lustre filesystem
///
/// # Examples
///
/// ```no_run
/// # use std::error::Error;
/// # fn example() -> Result<(), Box<dyn Error>> {
/// use rustreapi::Fid;
///
/// let fid = Fid::with_path("/mnt/lustre/myfile.txt")?;
/// println!("File FID: {}", fid);
/// # Ok(())
/// # }
/// ```
pub fn with_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut fid = lu_fid::default();
let cstr = CString::new(path.as_ref().to_string_lossy().to_string())?;
unsafe { cvt_nz(llapi_path2fid(cstr.as_ptr(), &mut fid as *mut lu_fid))? }
Ok(fid.into())
}
/// Converts this `Fid` to Lustre's native `lu_fid` structure.
///
/// This is useful when interfacing with the Lustre API functions
/// that require the native FID representation.
///
/// # Returns
///
/// A new `lu_fid` instance containing the same sequence, object ID and version.
pub fn to_lu_fid(&self) -> lu_fid {
lu_fid {
f_seq: self.seq,
f_oid: self.oid,
f_ver: self.ver,
}
}
}
impl From<lu_fid> for Fid {
fn from(fid: lu_fid) -> Self {
Self {
seq: fid.f_seq,
oid: fid.f_oid,
ver: fid.f_ver,
}
}
}
impl From<changelog_sys::lu_fid> for Fid {
fn from(fid: changelog_sys::lu_fid) -> Self {
Self {
seq: fid.f_seq,
oid: fid.f_oid,
ver: fid.f_ver,
}
}
}
impl From<Fid> for lu_fid {
fn from(fid: Fid) -> Self {
lu_fid {
f_seq: fid.seq,
f_oid: fid.oid,
f_ver: fid.ver,
}
}
}
impl Fid {
pub fn seq(&self) -> u64 {
self.seq
}
pub fn oid(&self) -> u32 {
self.oid
}
pub fn ver(&self) -> u32 {
self.ver
}
/// Checks if the FID is empty (all components are zero).
///
/// Returns `true` if `seq`, `oid`, and `ver` are all 0, `false` otherwise.
///
/// # Examples
///
/// ```
/// use rustreapi::Fid;
///
/// let empty_fid = Fid::new(0, 0, 0);
/// assert!(empty_fid.is_empty());
///
/// let non_empty_fid = Fid::new(1, 0, 0);
/// assert!(!non_empty_fid.is_empty());
/// ```
pub fn is_empty(&self) -> bool {
self.seq == 0 && self.oid == 0 && self.ver == 0
}
/// Opens a file identified by this FID through a Lustre filesystem descriptor.
///
/// # Arguments
///
/// * `lustre_fd` - An open file descriptor for a Lustre filesystem mount point
/// * `flags` - The open flags to use, defaults to `O_RDONLY` when set to 0
///
/// # Returns
///
/// * `Ok(File)` - The opened file handle
/// * `Err` - If the file cannot be opened using this FID
pub fn open_at<Fd: AsRawFd>(&self, lustre_fd: &Fd, flags: i32) -> Result<File> {
let fd = lustre_fd.as_raw_fd();
let flags = if flags == 0 { libc::O_RDONLY } else { flags };
unsafe {
cvt_lz_m(
llapi_open_by_fid_at(fd, &self.to_lu_fid(), flags),
"open_at".to_string(),
)
.map(|fd| File::from_raw_fd(fd))
}
}
/// Resolves this FID to its first path in the filesystem using a file descriptor
/// for the Lustre mount path..
///
/// This method converts a FID to its corresponding path in the Lustre filesystem
/// by querying the Lustre MDS. It requires an open file descriptor to a Lustre
/// mount point.
///
/// # Arguments
///
/// * `lustre_fd` - An open file descriptor to a Lustre filesystem mount point
///
/// # Returns
///
/// * `Ok(PathBuf)` - The path corresponding to this FID within the filesystem
/// * `Err` - If the path lookup fails (e.g., if the FID doesn't exist in this filesystem)
///
/// # Examples
///
/// ```no_run
/// # use std::error::Error;
/// # use std::fs::File;
/// # fn example() -> Result<(), Box<dyn Error>> {
/// use rustreapi::{Fid, LustrePath};
///
/// // Open a file descriptor to the Lustre mount
/// let mount_path = LustrePath::parse("/mnt/lustre")?;
/// let mount_file = File::open(&mount_path)?;
///
/// // Get the FID for a specific file
/// let fid = Fid::with_path(mount_path.as_ref().join("my_file.txt"))?;
///
/// // Convert the FID back to a path
/// let path = fid.path_at(&mount_file)?;
/// println!("FID resolves to path: {}", path.display());
/// # Ok(())
/// # }
pub fn path_at<Fd: AsRawFd>(&self, lustre_fd: &Fd) -> Result<PathBuf> {
let mut path = CStrBuf::new(MAX_PATH_LEN);
unsafe {
cvt_rc_m(
llapi_fid2path_at(
lustre_fd.as_raw_fd(),
&self.to_lu_fid(),
path.as_mut_ptr(),
path.buffer_len() as i32,
ptr::null_mut(),
ptr::null_mut(),
),
"path_at".to_string(),
)?
};
Ok(Path::new(&path).to_owned())
}
/// Resolves this FID to its path in the filesystem using a Lustre mount path.
///
/// This method is a convenience wrapper around `path_at()` that automatically
/// opens the provided Lustre path before resolving the FID to a path.
///
/// # Arguments
///
/// * `path` - A `LustrePath` representing a Lustre filesystem mount point
///
/// # Returns
///
/// * `Ok(PathBuf)` - The path corresponding to this FID within the filesystem
/// * `Err` - If opening the mount path fails or if the path lookup fails
///
/// # Examples
///
/// ```no_run
/// # use std::error::Error;
/// # fn example() -> Result<(), Box<dyn Error>> {
/// use rustreapi::{Fid, LustrePath};
///
/// // Parse a Lustre mount path
/// let mount_path = LustrePath::parse("/mnt/lustre")?;
///
/// // Get the FID for a specific file
/// let fid = Fid::with_path(mount_path.as_ref().join("my_file.txt"))?;
///
/// // Convert the FID back to a path
/// let path = fid.path(&mount_path)?;
/// println!("FID resolves to path: {}", path.display());
/// # Ok(())
/// # }
/// ```
pub fn path(&self, path: &LustrePath) -> Result<PathBuf> {
let file = path.open()?;
self.path_at(&file)
}
/// Resolves this FID to all of its paths (hard links) in the filesystem using a file descriptor.
///
/// This method converts a FID to all corresponding paths in the Lustre filesystem
/// by iterating through all hard links using the Lustre MDS. It requires an open
/// file descriptor to a Lustre mount point.
///
/// # Arguments
///
/// * `lustre_fd` - An open file descriptor to a Lustre filesystem mount point
///
/// # Returns
///
/// * `Ok(Vec<PathBuf>)` - All paths corresponding to this FID within the filesystem
/// * `Err` - If the path lookup fails (e.g., if the FID doesn't exist in this filesystem)
///
/// # Examples
///
/// ```no_run
/// # use std::error::Error;
/// # use std::fs::File;
/// # fn example() -> Result<(), Box<dyn Error>> {
/// use rustreapi::{Fid, LustrePath};
///
/// // Open a file descriptor to the Lustre mount
/// let mount_path = LustrePath::parse("/mnt/lustre")?;
/// let mount_file = File::open(&mount_path)?;
///
/// // Get the FID for a specific file
/// let fid = Fid::with_path(mount_path.as_ref().join("my_file.txt"))?;
///
/// // Get all paths (hard links) for this FID
/// let paths = fid.all_paths_at(&mount_file)?;
/// for path in paths {
/// println!("FID has hard link: {}", path.display());
/// }
/// # Ok(())
/// # }
/// ```
pub fn all_paths_at<Fd: AsRawFd>(&self, lustre_fd: &Fd) -> Result<Vec<PathBuf>> {
let mut paths = Vec::new();
let mut linkno: libc::c_int = 0;
// Safety limit to prevent infinite loops, not sure if this needs to be > 1000?
const MAX_LINKS: i32 = 1000;
loop {
if linkno >= MAX_LINKS {
break;
}
let mut path = CStrBuf::new(MAX_PATH_LEN);
let mut current_linkno = linkno;
let rc = unsafe {
llapi_fid2path_at(
lustre_fd.as_raw_fd(),
&self.to_lu_fid(),
path.as_mut_ptr(),
path.buffer_len() as i32,
ptr::null_mut(),
&mut current_linkno,
)
};
if rc == 0 {
// Success - add this path to our collection
let path_buf = Path::new(&path).to_owned();
// Avoid adding duplicate paths
if !paths.contains(&path_buf) {
paths.push(path_buf);
}
linkno += 1;
// If current_linkno didn't change or is invalid, we might be done
if current_linkno < linkno {
break;
}
} else {
// Error occurred - if we have no paths yet, this is a real error
if paths.is_empty() {
cvt_rc_m(rc, "all_paths_at".to_string())?;
}
// Otherwise, we've likely exhausted all available links
break;
}
}
Ok(paths)
}
/// Resolves this FID to all of its paths (hard links) in the filesystem using a Lustre mount path.
///
/// This method is a convenience wrapper around `all_paths_at()` that automatically
/// opens the provided Lustre path before resolving the FID to all paths.
///
/// # Arguments
///
/// * `path` - A `LustrePath` representing a Lustre filesystem mount point
///
/// # Returns
///
/// * `Ok(Vec<PathBuf>)` - All paths corresponding to this FID within the filesystem
/// * `Err` - If opening the mount path fails or if the path lookup fails
///
/// # Examples
///
/// ```no_run
/// # use std::error::Error;
/// # fn example() -> Result<(), Box<dyn Error>> {
/// use rustreapi::{Fid, LustrePath};
///
/// // Parse a Lustre mount path
/// let mount_path = LustrePath::parse("/mnt/lustre")?;
///
/// // Get the FID for a specific file
/// let fid = Fid::with_path(mount_path.as_ref().join("my_file.txt"))?;
///
/// // Get all paths (hard links) for this FID
/// let paths = fid.all_paths(&mount_path)?;
/// for path in paths {
/// println!("FID has hard link: {}", path.display());
/// }
/// # Ok(())
/// # }
/// ```
pub fn all_paths(&self, path: &LustrePath) -> Result<Vec<PathBuf>> {
let file = path.open()?;
self.all_paths_at(&file)
}
/// Parses a string representation of a Lustre FID into a `Fid` struct.
///
/// The string must contain three hexadecimal components in the format:
/// `[0xSEQ:0xOID:0xVER]` or `0xSEQ:0xOID:0xVER` (without brackets).
///
/// # Arguments
///
/// * `orig` - A string slice containing the FID representation.
///
/// # Returns
///
/// A `Result<Fid>` containing the parsed `Fid` or an error.
///
/// # Errors
///
/// Returns an error in the following cases:
/// - If the string doesn't contain exactly 3 components separated by colons
/// - If any component doesn't start with "0x" prefix
/// - If any component contains invalid hexadecimal digits
/// - If the sequence component overflows `u64`
/// - If the OID or version component overflows `u32`
///
/// # Examples
///
/// ```
/// use rustreapi::Fid;
///
/// let fid = Fid::parse("[0xCAFE:0x11:0x22]").unwrap();
/// assert_eq!(fid.to_string(), "[0xCAFE:0x11:0x22]");
///
/// let fid = Fid::parse("0x123:0x12:0x34").unwrap();
/// assert_eq!(fid.to_string(), "[0x123:0x12:0x34]");
/// ```
pub fn parse(orig: &str) -> Result<Fid> {
let s = orig.trim();
let s = if s.starts_with('[') && s.ends_with(']') {
&s[1..s.len() - 1] // Remove the optional surrounding brackets
} else {
s
};
let parts: Vec<&str> = s.split(':').collect();
if parts.len() == 3 {
if !parts[0].starts_with("0x") {
return Err(Error::InvalidFidFormat {
str: orig.to_string(),
});
}
let seq =
u64::from_str_radix(&parts[0][2..], 16).map_err(|e| Error::ParseFidError {
original: orig.to_string(),
part: parts[0][2..].to_string(),
err: e,
})?;
if !parts[1].starts_with("0x") {
return Err(Error::InvalidFidFormat {
str: orig.to_string(),
});
}
let oid =
u32::from_str_radix(&parts[1][2..], 16).map_err(|e| Error::ParseFidError {
original: orig.to_string(),
part: parts[1][2..].to_string(),
err: e,
})?;
if !parts[2].starts_with("0x") {
return Err(Error::InvalidFidFormat {
str: orig.to_string(),
});
}
let ver =
u32::from_str_radix(&parts[2][2..], 16).map_err(|e| Error::ParseFidError {
original: orig.to_string(),
part: parts[2][2..].to_string(),
err: e,
})?;
return Ok(Fid { seq, oid, ver });
}
Err(Error::InvalidFidFormat {
str: orig.to_string(),
})
}
}
impl Display for Fid {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "[0x{:X}:0x{:X}:0x{:X}]", self.seq, self.oid, self.ver)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_fid_into() {
let raw_fid = lu_fid::default();
let f: Fid = raw_fid.into();
assert_eq!(f.seq, 0);
assert_eq!(format!("{f}"), "[0x0:0x0:0x0]");
}
#[test]
fn test_fid_display() {
let f = Fid::new(16, 256, 1024);
assert_eq!(format!("{f}"), "[0x10:0x100:0x400]");
}
#[test]
fn test_fid_is_empty() {
// Test empty FID
let empty_fid = Fid::new(0, 0, 0);
assert!(empty_fid.is_empty());
// Test non-empty FIDs
let fid1 = Fid::new(1, 0, 0);
assert!(!fid1.is_empty());
let fid2 = Fid::new(0, 1, 0);
assert!(!fid2.is_empty());
let fid3 = Fid::new(0, 0, 1);
assert!(!fid3.is_empty());
let fid4 = Fid::new(123, 456, 789);
assert!(!fid4.is_empty());
}
#[test]
fn test_lu_fid_into() {
let fid = Fid::new(123, 456, 789);
let raw_fid: lu_fid = fid.into();
let f_seq = raw_fid.f_seq;
assert_eq!(f_seq, 123);
let f_oid = raw_fid.f_oid;
assert_eq!(f_oid, 456);
let f_ver = raw_fid.f_ver;
assert_eq!(f_ver, 789);
}
#[test]
fn test_fid_from() {
let raw_fid = lu_fid {
f_seq: 32,
f_oid: 64,
f_ver: 128,
};
let f = Fid::from(raw_fid);
let ptr = ptr::addr_of!(raw_fid.f_ver);
let ver = unsafe { ptr.read() };
assert_eq!(f.ver, ver);
}
#[test]
fn test_fid_parse() {
let str = "[0xCAFE:0x11:0x22]";
let fid = Fid::parse(str).expect("Fid should be valid.");
assert_eq!(str, fid.to_string());
}
#[test]
fn invalid_fid_missing_one() {
let str = "[0x12:0x34]";
let result = Fid::parse(str);
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(
e.to_string(),
Error::InvalidFidFormat {
str: str.to_string(),
}
.to_string(),
);
}
}
#[test]
fn invalid_fid_empty_seq() {
let str = "[0x:0x12:0x34]";
let result = Fid::parse(str);
assert!(result.is_err());
if let Err(e) = result {
insta::assert_debug_snapshot!(e.to_string(), @r#""parse fid error in [0x:0x12:0x34] '': cannot parse integer from empty string""#);
}
}
#[test]
fn invalid_fid_bad_seq() {
let str = "[fid:0x12:0x34]";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""invalid fid format: '[fid:0x12:0x34]'""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn invalid_fid_bad_seq2() {
let str = "[0xnotvalid:0x12:0x34]";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""parse fid error in [0xnotvalid:0x12:0x34] 'notvalid': invalid digit found in string""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn invalid_fid_empty_oid() {
let str = "[0x123:0x:0x34]";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""parse fid error in [0x123:0x:0x34] '': cannot parse integer from empty string""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn invalid_fid_bad_oid() {
let str = "[0x123:fid:0x34]";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""invalid fid format: '[0x123:fid:0x34]'""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn invalid_fid_bad_oid2() {
let str = "[0x123:0xinvalid:0x34]";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""parse fid error in [0x123:0xinvalid:0x34] 'invalid': invalid digit found in string""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn invalid_fid_oid_overflow() {
let str = "[0x123:0xffffffffffff:0x34]";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""parse fid error in [0x123:0xffffffffffff:0x34] 'ffffffffffff': number too large to fit in target type""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn invalid_fid_empty_ver() {
let str = "[0x123:0x12:0x]";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""parse fid error in [0x123:0x12:0x] '': cannot parse integer from empty string""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn invalid_fid_bad_ver() {
let str = "[0x123:0x313:fid]";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""invalid fid format: '[0x123:0x313:fid]'""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn invalid_fid_bad_ver2() {
let str = "[0x123:0x33:0xinvalid]";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""parse fid error in [0x123:0x33:0xinvalid] 'invalid': invalid digit found in string""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn invalid_fid_ver_overflow() {
let str = "[0x123:0x34:0xffffffffffff]";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""parse fid error in [0x123:0x34:0xffffffffffff] 'ffffffffffff': number too large to fit in target type""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn invalid_fid_empty() {
let str = "";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""invalid fid format: ''""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn fid_no_brackets() {
let str = "0x123:0x12:0x34";
let result = Fid::parse(str);
assert!(result.is_ok());
if let Ok(fid) = result {
assert_eq!(fid.seq, 0x123);
assert_eq!(fid.oid, 0x12);
assert_eq!(fid.ver, 0x34);
}
}
#[test]
fn fid_no_brackets_with_spaces() {
let str = " 0x123 : 0x12 : 0x34 ";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""parse fid error in 0x123 : 0x12 : 0x34 '123 ': invalid digit found in string""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
#[test]
fn too_many_parts() {
let str = "[0x123:0x12:0x34:0x56]";
let result = Fid::parse(str);
match result {
Err(e) => {
insta::assert_debug_snapshot!(e.to_string(), @r#""invalid fid format: '[0x123:0x12:0x34:0x56]'""#);
}
Ok(f) => {
panic!("Parsed a bad fid: {f}");
}
}
}
}