use crate::source::SourceSpan;
use super::{FieldReference, Located};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShortDevice {
raw: Located<String>,
kind: ShortDeviceKind,
}
impl ShortDevice {
pub(crate) fn new(raw: Located<String>) -> Self {
let kind = classify_short_device(raw.value());
Self { raw, kind }
}
#[must_use]
pub const fn raw(&self) -> &Located<String> {
&self.raw
}
#[must_use]
pub const fn kind(&self) -> ShortDeviceKind {
self.kind
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ShortDeviceKind {
Deferred,
Cdi,
Path,
Opaque,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LongDevice {
span: SourceSpan,
source: Option<Located<String>>,
target: Option<Located<String>>,
permissions: Option<Located<String>>,
extension_fields: Vec<FieldReference>,
unknown_fields: Vec<FieldReference>,
}
impl LongDevice {
pub(crate) const fn new(span: SourceSpan) -> Self {
Self {
span,
source: None,
target: None,
permissions: None,
extension_fields: Vec::new(),
unknown_fields: Vec::new(),
}
}
pub(crate) fn set_source(&mut self, source: Located<String>) {
self.source = Some(source);
}
pub(crate) fn set_target(&mut self, target: Located<String>) {
self.target = Some(target);
}
pub(crate) fn set_permissions(&mut self, permissions: Located<String>) {
self.permissions = Some(permissions);
}
pub(crate) fn push_extension(&mut self, field: FieldReference) {
self.extension_fields.push(field);
}
pub(crate) fn push_unknown(&mut self, field: FieldReference) {
self.unknown_fields.push(field);
}
#[must_use]
pub const fn span(&self) -> SourceSpan {
self.span
}
#[must_use]
pub const fn source(&self) -> Option<&Located<String>> {
self.source.as_ref()
}
#[must_use]
pub const fn target(&self) -> Option<&Located<String>> {
self.target.as_ref()
}
#[must_use]
pub const fn permissions(&self) -> Option<&Located<String>> {
self.permissions.as_ref()
}
#[must_use]
pub fn extension_fields(&self) -> &[FieldReference] {
&self.extension_fields
}
#[must_use]
pub fn unknown_fields(&self) -> &[FieldReference] {
&self.unknown_fields
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Device {
Short(ShortDevice),
Long(LongDevice),
}
impl Device {
#[must_use]
pub const fn span(&self) -> SourceSpan {
match self {
Self::Short(device) => device.raw().span(),
Self::Long(device) => device.span(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Devices {
span: SourceSpan,
items: Vec<Device>,
}
impl Devices {
pub(crate) const fn new(span: SourceSpan, items: Vec<Device>) -> Self {
Self { span, items }
}
#[must_use]
pub const fn span(&self) -> SourceSpan {
self.span
}
#[must_use]
pub fn items(&self) -> &[Device] {
&self.items
}
}
fn classify_short_device(value: &str) -> ShortDeviceKind {
if value.contains('$') {
return ShortDeviceKind::Deferred;
}
if value
.split_once('=')
.is_some_and(|(selector, name)| !selector.is_empty() && !name.is_empty() && selector.contains('/'))
{
return ShortDeviceKind::Cdi;
}
if value.starts_with('/') || value.starts_with('.') || value.contains(':') || value.starts_with(r"\\") {
return ShortDeviceKind::Path;
}
ShortDeviceKind::Opaque
}
pub(crate) fn valid_generated_device_string(value: &str, require_non_empty: bool) -> bool {
(!require_non_empty || !value.is_empty()) && !value.contains(['\0', '\r', '\n', '$'])
}
#[cfg(test)]
mod tests {
use super::{ShortDeviceKind, classify_short_device, valid_generated_device_string};
#[test]
fn classification_is_lexical_and_raw_preserving() {
assert_eq!(classify_short_device("/dev/dri:/dev/dri:rwm"), ShortDeviceKind::Path);
assert_eq!(classify_short_device("vendor.example/device=gpu"), ShortDeviceKind::Cdi);
assert_eq!(classify_short_device("${DEVICE}"), ShortDeviceKind::Deferred);
assert_eq!(classify_short_device("provider-token"), ShortDeviceKind::Opaque);
}
#[test]
fn generated_device_strings_only_enforce_safe_resolved_output() {
assert!(valid_generated_device_string("not-a-host-device", true));
assert!(valid_generated_device_string("not-permissions", false));
assert!(!valid_generated_device_string("", true));
assert!(!valid_generated_device_string("${DEVICE}", true));
assert!(!valid_generated_device_string("line\nbreak", false));
}
}