dicom_core/
ops.rs

1//! Module for the attribute operations API.
2//!
3//! This allows consumers to specify and implement
4//! operations on DICOM objects
5//! as part of a larger process,
6//! such as anonymization or transcoding.
7//!
8//! The most important type here is [`AttributeOp`],
9//! which indicates which attribute is affected ([`AttributeSelector`]),
10//! and the operation to apply ([`AttributeAction`]).
11//! All DICOM object types supporting this API
12//! implement the [`ApplyOp`] trait.
13//!
14//! # Example
15//!
16//! Given a DICOM object
17//! (opened using [`dicom_object`](https://docs.rs/dicom-object)),
18//! construct an [`AttributeOp`]
19//! and apply it using [`apply`](ApplyOp::apply).
20//!
21//! ```no_run
22//! # use dicom_core::Tag;
23//! use dicom_core::ops::*;
24//! # /* do not really import this
25//! use dicom_object::open_file;
26//! # */
27//!
28//! # struct DicomObj;
29//! # impl ApplyOp for DicomObj {
30//! #     type Err = snafu::Whatever;
31//! #     fn apply(&mut self, _: AttributeOp) -> Result<(), Self::Err> {
32//! #         panic!("this is just a stub");
33//! #     }
34//! # }
35//! # fn open_file(_: &str) -> Result<DicomObj, Box<dyn std::error::Error>> { Ok(DicomObj) }
36//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
37//! let mut obj = open_file("1/2/0003.dcm")?;
38//! // hide patient name
39//! obj.apply(AttributeOp::new(
40//!     Tag(0x0010, 0x0010),
41//!     AttributeAction::SetStr("Patient^Anonymous".into()),
42//! ))?;
43//! # Ok(())
44//! # }
45//! ```
46use std::{borrow::Cow, fmt::Write};
47
48use smallvec::{smallvec, SmallVec};
49
50use crate::{PrimitiveValue, Tag, VR};
51
52/// Descriptor for a single operation
53/// to apply over a DICOM data set.
54///
55/// This type is purely descriptive.
56/// It outlines a non-exhaustive set of possible changes around an attribute,
57/// as well as set some expectations regarding the outcome of certain actions
58/// against the attribute's previous state.
59///
60/// The operations themselves are provided
61/// alongside DICOM object or DICOM data set implementations,
62/// such as the `InMemDicomObject` from the [`dicom_object`] crate.
63///
64/// Attribute operations can only select shallow attributes,
65/// but the operation may be implemented when applied against nested data sets.
66///
67/// [`dicom_object`]: https://docs.rs/dicom_object
68#[derive(Debug, Clone, PartialEq)]
69pub struct AttributeOp {
70    /// the selector for the attribute to apply
71    pub selector: AttributeSelector,
72    /// the effective action to apply
73    pub action: AttributeAction,
74}
75
76impl AttributeOp {
77    /// Construct an attribute operation.
78    ///
79    /// This constructor function may be easier to use
80    /// than writing a public struct expression directly,
81    /// due to its automatic conversion of `selector`.
82    ///
83    /// # Example
84    ///
85    /// ```
86    /// # use dicom_core::Tag;
87    /// # use dicom_core::ops::{AttributeAction, AttributeOp};
88    /// let op = AttributeOp::new(
89    ///     // ImageType
90    ///     Tag(0x0008, 0x0008),
91    ///     AttributeAction::SetStr("DERIVED\\SECONDARY\\DOSE_INFO".into()),
92    /// );
93    /// ```
94    pub fn new(selector: impl Into<AttributeSelector>, action: AttributeAction) -> Self {
95        AttributeOp {
96            selector: selector.into(),
97            action,
98        }
99    }
100}
101
102/// A single step of an attribute selection.
103///
104/// A selector step may either select an element directly at the root (`Tag`)
105/// or a specific item in a sequence to navigate into (`Nested`).
106///
107/// A full attribute selector can be specified
108/// by using a sequence of these steps
109/// (but should always end with the `Tag` variant,
110/// otherwise the operation would be unspecified).
111#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
112pub enum AttributeSelectorStep {
113    /// Select the element with the tag reachable at the root of this data set
114    Tag(Tag),
115    /// Select an item in a data set sequence,
116    /// as an intermediate step
117    Nested { tag: Tag, item: u32 },
118}
119
120impl From<Tag> for AttributeSelectorStep {
121    /// Creates an attribute selector step by data element tag.
122    fn from(value: Tag) -> Self {
123        AttributeSelectorStep::Tag(value)
124    }
125}
126
127impl From<(Tag, u32)> for AttributeSelectorStep {
128    /// Creates a sequence item selector step
129    /// by data element tag and item index.
130    fn from((tag, item): (Tag, u32)) -> Self {
131        AttributeSelectorStep::Nested { tag, item }
132    }
133}
134
135impl std::fmt::Display for AttributeSelectorStep {
136    /// Displays the attribute selector step:
137    /// `(GGGG,EEEE)` if `Tag`,,
138    /// `(GGGG,EEEE)[i]` if `Nested`
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        match self {
141            AttributeSelectorStep::Tag(tag) => std::fmt::Display::fmt(tag, f),
142            AttributeSelectorStep::Nested { tag, item } => write!(f, "{}[{}]", tag, item),
143        }
144    }
145}
146
147/// An attribute selector.
148///
149/// This type defines the path to an element in a DICOM data set,
150/// even at an arbitrary depth of nested data sets.
151/// A selector may be perceived as a series of navigation steps
152/// to reach a certain data element,
153/// where all steps but the last one refer to data set sequences.
154///
155/// Attribute selectors can be created through
156/// one of the various [`From`] conversions,
157/// the dynamic constructor function [`new`],
158/// or through parsing.
159///
160/// # Syntax
161///
162/// A syntax is defined for the unambiguous conversion
163/// between a string and an `AttributeSelector` value,
164/// in both directions.
165/// Attribute selectors are defined by the syntax
166/// `( «key»([«item»])? . )* «key» `
167/// where:
168///
169/// - _`«key»`_ is either a DICOM tag in a supported textual form,
170///   or a tag keyword as accepted by the [data dictionary][dict] in use;
171/// - _`«item»`_ is an unsigned integer representing the item index,
172///   which is always surrounded by square brackets in the input;
173/// - _`[`_, _`]`_, and _`.`_ are literally their own characters
174///   as part of the input.
175///
176/// [dict]: crate::dictionary::DataDictionary
177///
178/// The first part in parentheses may appear zero or more times.
179/// The `[«item»]` part can be omitted,
180/// in which case it is assumed that the first item is selected.
181/// Whitespace is not admitted in any position.
182/// Displaying a selector through the [`Display`](std::fmt::Display) trait
183/// produces a string that is compliant with this syntax.
184///
185/// ### Examples of attribute selectors in text:
186///
187/// - `(0002,00010)`:
188///   selects _Transfer Syntax UID_
189/// - `00101010`:
190///   selects _Patient Age_
191/// - `0040A168[0].CodeValue`:
192///   selects _Code Value_ within the first item of _Concept Code Sequence_
193/// - `0040,A730[1].ContentSequence`:
194///   selects _Content Sequence_ in second item of _Content Sequence_
195/// - `SequenceOfUltrasoundRegions.RegionSpatialFormat`:
196///   _Region Spatial Format_ in first item of _Sequence of Ultrasound Regions_
197///
198/// # Example
199///
200/// In most cases, you might only wish to select an attribute
201/// that is sitting at the root of the data set.
202/// This can be done by converting a [DICOM tag](crate::Tag) via [`From<Tag>`]:
203///
204/// ```
205/// # use dicom_core::Tag;
206/// # use dicom_core::ops::AttributeSelector;
207/// // select Patient Name
208/// let selector = AttributeSelector::from(Tag(0x0010, 0x0010));
209/// ```
210///
211/// For working with nested data sets,
212/// `From` also supports converting
213/// an interleaved sequence of tags and item indices in a tuple.
214/// For instance,
215/// this is how we can select the second frame's acquisition date time
216/// from the per-frame functional groups sequence.
217///
218/// ```
219/// # use dicom_core::Tag;
220/// # use dicom_core::ops::AttributeSelector;
221/// let selector: AttributeSelector = (
222///     // Per-frame functional groups sequence
223///     Tag(0x5200, 0x9230),
224///     // item #1
225///     1,
226///     // Frame Acquisition Date Time (DT)
227///     Tag(0x0018, 0x9074)
228/// ).into();
229/// ```
230///
231/// For a more dynamic construction,
232/// the [`new`] function supports an iterator of attribute selector steps
233/// (of type [`AttributeSelectorStep`]).
234/// Note that the function fails
235/// if the last step refers to a sequence item.
236///
237/// [`new`]: AttributeSelector::new
238///
239/// ```
240/// # use dicom_core::Tag;
241/// # use dicom_core::ops::{AttributeSelector, AttributeSelectorStep};
242/// let selector = AttributeSelector::new([
243///     // Per-frame functional groups sequence, item #1
244///     AttributeSelectorStep::Nested {
245///         tag: Tag(0x5200, 0x9230),
246///         // item #1
247///         item: 1,
248///     },
249///     // Frame Acquisition Date Time
250///     AttributeSelectorStep::Tag(Tag(0x0018, 0x9074)),
251/// ]).ok_or_else(|| "should be a valid sequence")?;
252/// # let selector: AttributeSelector = selector;
253/// # Result::<_, &'static str>::Ok(())
254/// ```
255///
256/// A data dictionary's [`parse_selector`][parse] method
257/// can be used if you want to describe these selectors in text.
258///
259/// ```no_run
260/// # // compile only: we don't have the std dict here
261/// # use dicom_core::{Tag, ops::AttributeSelector};
262/// use dicom_core::dictionary::DataDictionary;
263/// # use dicom_core::dictionary::stub::StubDataDictionary;
264/// # /* faking an import
265/// use dicom_dictionary_std::StandardDataDictionary;
266/// # */
267///
268/// # let StandardDataDictionary = StubDataDictionary;
269/// assert_eq!(
270///     StandardDataDictionary.parse_selector(
271///         "PerFrameFunctionalGroupsSequence[1].(0018,9074)"
272///     )?,
273///     AttributeSelector::from((
274///         // Per-frame functional groups sequence
275///         Tag(0x5200, 0x9230),
276///         // item #1
277///         1,
278///         // Frame Acquisition Date Time (DT)
279///         Tag(0x0018, 0x9074)
280///     )),
281/// );
282/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
283/// ```
284///
285/// [parse]: crate::dictionary::DataDictionary::parse_selector
286///
287/// Selectors can be decomposed back into its constituent steps
288/// by turning it into an iterator:
289///
290/// ```
291/// # use dicom_core::Tag;
292/// # use dicom_core::ops::{AttributeSelector, AttributeSelectorStep};
293/// # let selector = AttributeSelector::from(
294/// #     (Tag(0x5200, 0x9230), 1, Tag(0x0018, 0x9074)));
295/// let steps: Vec<AttributeSelectorStep> = selector.into_iter().collect();
296///
297/// assert_eq!(
298///     &steps,
299///     &[
300///         AttributeSelectorStep::Nested {
301///             tag: Tag(0x5200, 0x9230),
302///             item: 1,
303///         },
304///         AttributeSelectorStep::Tag(Tag(0x0018, 0x9074)),
305///     ],
306/// );
307/// ```
308///
309#[derive(Debug, Clone, Eq, Hash, PartialEq)]
310pub struct AttributeSelector(SmallVec<[AttributeSelectorStep; 2]>);
311
312impl AttributeSelector {
313    /// Construct an attribute selector
314    /// from an arbitrary sequence of selector steps.
315    ///
316    /// Intermediate steps of variant [`Tag`][1]
317    /// (which do not specify an item index)
318    /// are automatically reinterpreted as item selectors for item index 0.
319    ///
320    /// Returns `None` if the sequence is empty
321    /// or the last step is not a tag selector step.
322    ///
323    /// [1]: AttributeSelectorStep::Tag
324    pub fn new(steps: impl IntoIterator<Item = AttributeSelectorStep>) -> Option<Self> {
325        let mut steps: SmallVec<_> = steps.into_iter().collect();
326        let (last, rest) = steps.split_last_mut()?;
327        if matches!(last, AttributeSelectorStep::Nested { .. }) {
328            return None;
329        }
330        // transform intermediate `Tag` steps into the `Nested` variant
331        for step in rest {
332            if let AttributeSelectorStep::Tag(tag) = step {
333                *step = AttributeSelectorStep::Nested { tag: *tag, item: 0 };
334            }
335        }
336        Some(AttributeSelector(steps))
337    }
338
339    /// Return a non-empty iterator over the steps of attribute selection.
340    ///
341    /// The iterator is guaranteed to produce a series
342    /// starting with zero or more steps of the variant [`Nested`][1],
343    /// and terminated by one item guaranteed to be a [tag][2].
344    ///
345    /// [1]: AttributeSelectorStep::Nested
346    /// [2]: AttributeSelectorStep::Tag
347    pub fn iter(&self) -> impl Iterator<Item = &AttributeSelectorStep> {
348        self.into_iter()
349    }
350
351    /// Obtain a reference to the first attribute selection step.
352    pub fn first_step(&self) -> &AttributeSelectorStep {
353        // guaranteed not to be empty
354        self.0
355            .first()
356            .expect("invariant broken: attribute selector should have at least one step")
357    }
358
359    /// Obtain a reference to the last attribute selection step.
360    pub fn last_step(&self) -> &AttributeSelectorStep {
361        // guaranteed not to be empty
362        self.0
363            .last()
364            .expect("invariant broken: attribute selector should have at least one step")
365    }
366
367    /// Obtain the tag of the last attribute selection step.
368    pub fn last_tag(&self) -> Tag {
369        match self.last_step() {
370            AttributeSelectorStep::Tag(tag) => *tag,
371            _ => unreachable!("invariant broken: last attribute selector step should be Tag"),
372        }
373    }
374}
375
376impl IntoIterator for AttributeSelector {
377    type Item = AttributeSelectorStep;
378    type IntoIter = <SmallVec<[AttributeSelectorStep; 2]> as IntoIterator>::IntoIter;
379
380    /// Returns a non-empty iterator over the steps of attribute selection.
381    ///
382    /// The iterator is guaranteed to produce a series
383    /// starting with zero or more steps of the variant [`Nested`][1],
384    /// and terminated by one item guaranteed to be a [tag][2].
385    ///
386    /// [1]: AttributeSelectorStep::Nested
387    /// [2]: AttributeSelectorStep::Tag
388    fn into_iter(self) -> Self::IntoIter {
389        self.0.into_iter()
390    }
391}
392
393impl<'a> IntoIterator for &'a AttributeSelector {
394    type Item = &'a AttributeSelectorStep;
395    type IntoIter = <&'a SmallVec<[AttributeSelectorStep; 2]> as IntoIterator>::IntoIter;
396
397    /// Returns a non-empty iterator over the steps of attribute selection.
398    ///
399    /// The iterator is guaranteed to produce a series
400    /// starting with zero or more steps of the variant [`Nested`][1],
401    /// and terminated by one item guaranteed to be a [tag][2].
402    ///
403    /// [1]: AttributeSelectorStep::Nested
404    /// [2]: AttributeSelectorStep::Tag
405    fn into_iter(self) -> Self::IntoIter {
406        self.0.iter()
407    }
408}
409
410/// Creates an attribute selector for just a [`tag`](AttributeSelectorStep::Tag).
411impl From<Tag> for AttributeSelector {
412    /// Creates a simple attribute selector
413    /// by selecting the element at the data set root with the given DICOM tag.
414    fn from(tag: Tag) -> Self {
415        AttributeSelector(smallvec![tag.into()])
416    }
417}
418
419/// Creates an attribute selector for `tag[item].tag`
420impl From<(Tag, u32, Tag)> for AttributeSelector {
421    /// Creates an attribute selector
422    /// which navigates to the data set item at index `item`
423    /// in the sequence at the first DICOM tag (`tag0`),
424    /// then selects the element with the second DICOM tag (`tag1`).
425    fn from((tag0, item, tag1): (Tag, u32, Tag)) -> Self {
426        AttributeSelector(smallvec![(tag0, item).into(), tag1.into()])
427    }
428}
429
430/// Creates an attribute selector for `tag.tag`
431/// (where the first)
432impl From<(Tag, Tag)> for AttributeSelector {
433    /// Creates an attribute selector
434    /// which navigates to the first data set item
435    /// in the sequence at the first DICOM tag (`tag0`),
436    /// then selects the element with the second DICOM tag (`tag1`).
437    #[inline]
438    fn from((tag0, tag1): (Tag, Tag)) -> Self {
439        AttributeSelector(smallvec![(tag0, 0).into(), tag1.into()])
440    }
441}
442
443/// Creates an attribute selector for `tag[item].tag[item].tag`
444impl From<(Tag, u32, Tag, u32, Tag)> for AttributeSelector {
445    /// Creates an attribute selector
446    /// which navigates to data set item #`item0`
447    /// in the sequence at `tag0`,
448    /// navigates further down to item #`item1` in the sequence at `tag1`,
449    /// then selects the element at `tag2`.
450    fn from((tag0, item0, tag1, item1, tag2): (Tag, u32, Tag, u32, Tag)) -> Self {
451        AttributeSelector(smallvec![
452            (tag0, item0).into(),
453            (tag1, item1).into(),
454            tag2.into()
455        ])
456    }
457}
458
459/// Creates an attribute selector for `tag.tag[item].tag`
460impl From<(Tag, Tag, u32, Tag)> for AttributeSelector {
461    /// Creates an attribute selector
462    /// which navigates to the first data set item
463    /// in the sequence at `tag0`,
464    /// navigates further down to item #`item1` in the sequence at `tag1`,
465    /// then selects the element at `tag2`.
466    fn from((tag0, tag1, item1, tag2): (Tag, Tag, u32, Tag)) -> Self {
467        AttributeSelector(smallvec![
468            (tag0, 0).into(),
469            (tag1, item1).into(),
470            tag2.into()
471        ])
472    }
473}
474
475/// Creates an attribute selector for `tag[item].tag.tag`
476impl From<(Tag, u32, Tag, Tag)> for AttributeSelector {
477    /// Creates an attribute selector
478    /// which navigates to the data set item #`item0`
479    /// in the sequence at `tag0`,
480    /// navigates further down to the first item in the sequence at `tag1`,
481    /// then selects the element at `tag2`.
482    fn from((tag0, item0, tag1, tag2): (Tag, u32, Tag, Tag)) -> Self {
483        AttributeSelector(smallvec![
484            (tag0, item0).into(),
485            (tag1, 0).into(),
486            tag2.into()
487        ])
488    }
489}
490
491/// Creates an attribute selector for `tag.tag.tag`
492impl From<(Tag, Tag, Tag)> for AttributeSelector {
493    /// Creates an attribute selector
494    /// which navigates to the first data set item
495    /// in the sequence at `tag0`,
496    /// navigates further down to the first item in the sequence at `tag1`,
497    /// then selects the element at `tag2`.
498    fn from((tag0, tag1, tag2): (Tag, Tag, Tag)) -> Self {
499        AttributeSelector(smallvec![(tag0, 0).into(), (tag1, 0).into(), tag2.into()])
500    }
501}
502
503/// Creates an attribute selector for `tag[item].tag[item].tag[item].tag`
504impl From<(Tag, u32, Tag, u32, Tag, u32, Tag)> for AttributeSelector {
505    // you should get the gist at this point
506    fn from(
507        (tag0, item0, tag1, item1, tag2, item2, tag3): (Tag, u32, Tag, u32, Tag, u32, Tag),
508    ) -> Self {
509        AttributeSelector(smallvec![
510            (tag0, item0).into(),
511            (tag1, item1).into(),
512            (tag2, item2).into(),
513            tag3.into()
514        ])
515    }
516}
517
518impl std::fmt::Display for AttributeSelector {
519    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
520        let mut started = false;
521        for step in &self.0 {
522            if !started {
523                started = true;
524            } else {
525                // separate each step by a dot
526                f.write_char('.')?;
527            }
528            std::fmt::Display::fmt(step, f)?;
529        }
530        Ok(())
531    }
532}
533
534/// Descriptor for the kind of action to apply over an attribute.
535///
536/// See the [module-level documentation](crate::ops)
537/// for more details.
538#[non_exhaustive]
539#[derive(Debug, Clone, PartialEq)]
540pub enum AttributeAction {
541    /// Remove the attribute if it exists.
542    ///
543    /// Do nothing otherwise.
544    Remove,
545    /// If the attribute exists, clear its value to zero bytes.
546    Empty,
547    /// If the attribute exists,
548    /// set or provide a hint about the attribute's value representation.
549    ///
550    /// The underlying value is not modified.
551    /// Implementations are free to ignore this request if
552    /// it cannot be done or it does not make sense
553    /// for the given implementation.
554    SetVr(VR),
555    /// Fully reset the attribute with the given DICOM value,
556    /// creating it if it does not exist yet.
557    ///
558    /// For objects supporting nested data sets,
559    /// passing [`PrimitiveValue::Empty`] will create
560    /// an empty data set sequence.
561    Set(PrimitiveValue),
562    /// Fully reset a textual attribute with the given string,
563    /// creating it if it does not exist yet.
564    SetStr(Cow<'static, str>),
565    /// Provide the attribute with the given DICOM value,
566    /// if it does not exist yet.
567    ///
568    /// For objects supporting nested data sets,
569    /// passing [`PrimitiveValue::Empty`] will create
570    /// an empty data set sequence.
571    SetIfMissing(PrimitiveValue),
572    /// Provide the textual attribute with the given string,
573    /// creating it if it does not exist yet.
574    SetStrIfMissing(Cow<'static, str>),
575    /// Fully replace the value with the given DICOM value,
576    /// but only if the attribute already exists.
577    ///
578    /// For objects supporting nested data sets,
579    /// passing [`PrimitiveValue::Empty`] will clear the items
580    /// of an existing data set sequence.
581    Replace(PrimitiveValue),
582    /// Fully replace a textual value with the given string,
583    /// but only if the attribute already exists.
584    ReplaceStr(Cow<'static, str>),
585    /// Append a string as an additional textual value,
586    /// creating the attribute if it does not exist yet.
587    ///
588    /// New value items are recorded as separate text values,
589    /// meaning that they are delimited by a backslash (`\`) at encoding time,
590    /// regardless of the value representation.
591    PushStr(Cow<'static, str>),
592    /// Append a 32-bit signed integer as an additional numeric value,
593    /// creating the attribute if it does not exist yet.
594    PushI32(i32),
595    /// Append a 32-bit unsigned integer as an additional numeric value,
596    /// creating the attribute if it does not exist yet.
597    PushU32(u32),
598    /// Append a 16-bit signed integer as an additional numeric value,
599    /// creating the attribute if it does not exist yet.
600    PushI16(i16),
601    /// Append a 16-bit unsigned integer as an additional numeric value,
602    /// creating the attribute if it does not exist yet.
603    PushU16(u16),
604    /// Append a 32-bit floating point number as an additional numeric value,
605    /// creating the attribute if it does not exist yet.
606    PushF32(f32),
607    /// Append a 64-bit floating point number as an additional numeric value,
608    /// creating the attribute if it does not exist yet.
609    PushF64(f64),
610    /// Truncate a value or a sequence to the given number of items,
611    /// removing extraneous items from the end of the list.
612    ///
613    /// On primitive values, this truncates the value
614    /// by the number of individual value items
615    /// (note that bytes in a [`PrimitiveValue::U8`]
616    /// are treated as individual items).
617    /// On data set sequences and pixel data fragment sequences,
618    /// this operation is applied to
619    /// the data set items (or fragments) in the sequence.
620    ///
621    /// Does nothing if the attribute does not exist
622    /// or the cardinality of the element is already lower than or equal to
623    /// the given size.
624    Truncate(usize),
625}
626
627impl AttributeAction {
628    /// Report whether this is considered a _constructive_ action,
629    /// operations of which create new elements if they do not exist yet.
630    ///
631    /// The actions currently considered to be constructive are
632    /// all actions of the families `Set*`, `SetIfMissing`, and `Push*`.
633    pub fn is_constructive(&self) -> bool {
634        matches!(
635            self,
636            AttributeAction::Set(_)
637                | AttributeAction::SetStr(_)
638                | AttributeAction::SetIfMissing(_)
639                | AttributeAction::SetStrIfMissing(_)
640                | AttributeAction::PushF32(_)
641                | AttributeAction::PushF64(_)
642                | AttributeAction::PushI16(_)
643                | AttributeAction::PushI32(_)
644                | AttributeAction::PushStr(_)
645                | AttributeAction::PushU16(_)
646                | AttributeAction::PushU32(_)
647        )
648    }
649}
650
651/// Trait for applying DICOM attribute operations.
652///
653/// This is typically implemented by DICOM objects and other data set types
654/// to serve as a common API for attribute manipulation.
655pub trait ApplyOp {
656    /// The operation error type
657    type Err: std::error::Error + 'static;
658
659    /// Apply the given attribute operation on the receiving object.
660    ///
661    /// Effects may slightly differ between implementations,
662    /// but should always be compliant with
663    /// the expectations defined in [`AttributeAction`] variants.
664    ///
665    /// If the action to apply is unsupported,
666    /// or not possible for other reasons,
667    /// an error is returned and no changes to the receiver are made.
668    /// While not all kinds of operations may be possible,
669    /// generic DICOM data set holders will usually support all actions.
670    /// See the respective documentation of the implementing type
671    /// for more details.
672    fn apply(&mut self, op: AttributeOp) -> Result<(), Self::Err>;
673}
674
675#[cfg(test)]
676mod tests {
677    use crate::{ops::AttributeSelector, Tag};
678
679    #[test]
680    fn display_selectors() {
681        let selector: AttributeSelector = Tag(0x0014, 0x5100).into();
682        assert_eq!(selector.to_string(), "(0014,5100)",);
683
684        let selector: AttributeSelector = (Tag(0x0018, 0x6011), 2, Tag(0x0018, 0x6012)).into();
685        assert_eq!(selector.to_string(), "(0018,6011)[2].(0018,6012)",);
686
687        let selector = AttributeSelector::from((Tag(0x0040, 0xA730), 1, Tag(0x0040, 0xA730)));
688        assert_eq!(selector.to_string(), "(0040,A730)[1].(0040,A730)",);
689    }
690}