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        debug_assert!(steps.len() < 256);
327        let (last, rest) = steps.split_last_mut()?;
328        if matches!(last, AttributeSelectorStep::Nested { .. }) {
329            return None;
330        }
331        // transform intermediate `Tag` steps into the `Nested` variant
332        for step in rest {
333            if let AttributeSelectorStep::Tag(tag) = step {
334                *step = AttributeSelectorStep::Nested { tag: *tag, item: 0 };
335            }
336        }
337        Some(AttributeSelector(steps))
338    }
339
340    /// Split this attribute selector into the first step
341    /// and its remainder.
342    ///
343    /// If the first part of the tuple is the last step of the selector,
344    /// the first item will be of the variant [`Tag`](AttributeSelectorStep::Tag)
345    /// and the second item of the tuple will be `None`.
346    /// Otherwise,
347    /// the first item is guaranteed to be of the variant
348    /// [`Nested`](AttributeSelectorStep::Nested).
349    pub fn split_first(&self) -> (AttributeSelectorStep, Option<AttributeSelector>) {
350        match self.0.split_first() {
351            Some((first, rest)) => {
352                let rest = if rest.is_empty() {
353                    None
354                } else {
355                    Some(AttributeSelector(rest.into()))
356                };
357                (*first, rest)
358            }
359            None => {
360                unreachable!("invariant broken: attribute selector should have at least one step")
361            }
362        }
363    }
364
365    /// Return a non-empty iterator over the steps of attribute selection.
366    ///
367    /// The iterator is guaranteed to produce a series
368    /// starting with zero or more steps of the variant [`Nested`][1],
369    /// and terminated by one item guaranteed to be a [tag][2].
370    ///
371    /// [1]: AttributeSelectorStep::Nested
372    /// [2]: AttributeSelectorStep::Tag
373    pub fn iter(&self) -> impl Iterator<Item = &AttributeSelectorStep> {
374        self.into_iter()
375    }
376
377    /// Obtain a reference to the first attribute selection step.
378    pub fn first_step(&self) -> &AttributeSelectorStep {
379        // guaranteed not to be empty
380        self.0
381            .first()
382            .expect("invariant broken: attribute selector should have at least one step")
383    }
384
385    /// Obtain a reference to the last attribute selection step.
386    pub fn last_step(&self) -> &AttributeSelectorStep {
387        // guaranteed not to be empty
388        self.0
389            .last()
390            .expect("invariant broken: attribute selector should have at least one step")
391    }
392
393    /// Obtain the tag of the last attribute selection step.
394    pub fn last_tag(&self) -> Tag {
395        match self.last_step() {
396            AttributeSelectorStep::Tag(tag) => *tag,
397            _ => unreachable!("invariant broken: last attribute selector step should be Tag"),
398        }
399    }
400
401    /// Obtain the number of steps of the selector.
402    ///
403    /// Since selectors cannot be empty,
404    /// the number of steps is always larger than zero.
405    pub fn num_steps(&self) -> u32 {
406        self.0.len() as u32
407    }
408}
409
410impl IntoIterator for AttributeSelector {
411    type Item = AttributeSelectorStep;
412    type IntoIter = <SmallVec<[AttributeSelectorStep; 2]> as IntoIterator>::IntoIter;
413
414    /// Returns a non-empty iterator over the steps of attribute selection.
415    ///
416    /// The iterator is guaranteed to produce a series
417    /// starting with zero or more steps of the variant [`Nested`][1],
418    /// and terminated by one item guaranteed to be a [tag][2].
419    ///
420    /// [1]: AttributeSelectorStep::Nested
421    /// [2]: AttributeSelectorStep::Tag
422    fn into_iter(self) -> Self::IntoIter {
423        self.0.into_iter()
424    }
425}
426
427impl<'a> IntoIterator for &'a AttributeSelector {
428    type Item = &'a AttributeSelectorStep;
429    type IntoIter = <&'a SmallVec<[AttributeSelectorStep; 2]> as IntoIterator>::IntoIter;
430
431    /// Returns a non-empty iterator over the steps of attribute selection.
432    ///
433    /// The iterator is guaranteed to produce a series
434    /// starting with zero or more steps of the variant [`Nested`][1],
435    /// and terminated by one item guaranteed to be a [tag][2].
436    ///
437    /// [1]: AttributeSelectorStep::Nested
438    /// [2]: AttributeSelectorStep::Tag
439    fn into_iter(self) -> Self::IntoIter {
440        self.0.iter()
441    }
442}
443
444/// Creates an attribute selector for just a [`tag`](AttributeSelectorStep::Tag).
445impl From<Tag> for AttributeSelector {
446    /// Creates a simple attribute selector
447    /// by selecting the element at the data set root with the given DICOM tag.
448    fn from(tag: Tag) -> Self {
449        AttributeSelector(smallvec![tag.into()])
450    }
451}
452
453/// Creates an attribute selector for `tag[item].tag`
454impl From<(Tag, u32, Tag)> for AttributeSelector {
455    /// Creates an attribute selector
456    /// which navigates to the data set item at index `item`
457    /// in the sequence at the first DICOM tag (`tag0`),
458    /// then selects the element with the second DICOM tag (`tag1`).
459    fn from((tag0, item, tag1): (Tag, u32, Tag)) -> Self {
460        AttributeSelector(smallvec![(tag0, item).into(), tag1.into()])
461    }
462}
463
464/// Creates an attribute selector for `tag.tag`
465/// (where the first)
466impl From<(Tag, Tag)> for AttributeSelector {
467    /// Creates an attribute selector
468    /// which navigates to the first data set item
469    /// in the sequence at the first DICOM tag (`tag0`),
470    /// then selects the element with the second DICOM tag (`tag1`).
471    #[inline]
472    fn from((tag0, tag1): (Tag, Tag)) -> Self {
473        AttributeSelector(smallvec![(tag0, 0).into(), tag1.into()])
474    }
475}
476
477/// Creates an attribute selector for `tag[item].tag[item].tag`
478impl From<(Tag, u32, Tag, u32, Tag)> for AttributeSelector {
479    /// Creates an attribute selector
480    /// which navigates to data set item #`item0`
481    /// in the sequence at `tag0`,
482    /// navigates further down to item #`item1` in the sequence at `tag1`,
483    /// then selects the element at `tag2`.
484    fn from((tag0, item0, tag1, item1, tag2): (Tag, u32, Tag, u32, Tag)) -> Self {
485        AttributeSelector(smallvec![
486            (tag0, item0).into(),
487            (tag1, item1).into(),
488            tag2.into()
489        ])
490    }
491}
492
493/// Creates an attribute selector for `tag.tag[item].tag`
494impl From<(Tag, Tag, u32, Tag)> for AttributeSelector {
495    /// Creates an attribute selector
496    /// which navigates to the first data set item
497    /// in the sequence at `tag0`,
498    /// navigates further down to item #`item1` in the sequence at `tag1`,
499    /// then selects the element at `tag2`.
500    fn from((tag0, tag1, item1, tag2): (Tag, Tag, u32, Tag)) -> Self {
501        AttributeSelector(smallvec![
502            (tag0, 0).into(),
503            (tag1, item1).into(),
504            tag2.into()
505        ])
506    }
507}
508
509/// Creates an attribute selector for `tag[item].tag.tag`
510impl From<(Tag, u32, Tag, Tag)> for AttributeSelector {
511    /// Creates an attribute selector
512    /// which navigates to the data set item #`item0`
513    /// in the sequence at `tag0`,
514    /// navigates further down to the first item in the sequence at `tag1`,
515    /// then selects the element at `tag2`.
516    fn from((tag0, item0, tag1, tag2): (Tag, u32, Tag, Tag)) -> Self {
517        AttributeSelector(smallvec![
518            (tag0, item0).into(),
519            (tag1, 0).into(),
520            tag2.into()
521        ])
522    }
523}
524
525/// Creates an attribute selector for `tag.tag.tag`
526impl From<(Tag, Tag, Tag)> for AttributeSelector {
527    /// Creates an attribute selector
528    /// which navigates to the first data set item
529    /// in the sequence at `tag0`,
530    /// navigates further down to the first item in the sequence at `tag1`,
531    /// then selects the element at `tag2`.
532    fn from((tag0, tag1, tag2): (Tag, Tag, Tag)) -> Self {
533        AttributeSelector(smallvec![(tag0, 0).into(), (tag1, 0).into(), tag2.into()])
534    }
535}
536
537/// Creates an attribute selector for `tag[item].tag[item].tag[item].tag`
538impl From<(Tag, u32, Tag, u32, Tag, u32, Tag)> for AttributeSelector {
539    // you should get the gist at this point
540    fn from(
541        (tag0, item0, tag1, item1, tag2, item2, tag3): (Tag, u32, Tag, u32, Tag, u32, Tag),
542    ) -> Self {
543        AttributeSelector(smallvec![
544            (tag0, item0).into(),
545            (tag1, item1).into(),
546            (tag2, item2).into(),
547            tag3.into()
548        ])
549    }
550}
551
552impl std::fmt::Display for AttributeSelector {
553    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
554        let mut started = false;
555        for step in &self.0 {
556            if !started {
557                started = true;
558            } else {
559                // separate each step by a dot
560                f.write_char('.')?;
561            }
562            std::fmt::Display::fmt(step, f)?;
563        }
564        Ok(())
565    }
566}
567
568/// Descriptor for the kind of action to apply over an attribute.
569///
570/// See the [module-level documentation](crate::ops)
571/// for more details.
572#[non_exhaustive]
573#[derive(Debug, Clone, PartialEq)]
574pub enum AttributeAction {
575    /// Remove the attribute if it exists.
576    ///
577    /// Do nothing otherwise.
578    Remove,
579    /// If the attribute exists, clear its value to zero bytes.
580    Empty,
581    /// If the attribute exists,
582    /// set or provide a hint about the attribute's value representation.
583    ///
584    /// The underlying value is not modified.
585    /// Implementations are free to ignore this request if
586    /// it cannot be done or it does not make sense
587    /// for the given implementation.
588    SetVr(VR),
589    /// Fully reset the attribute with the given DICOM value,
590    /// creating it if it does not exist yet.
591    ///
592    /// For objects supporting nested data sets,
593    /// passing [`PrimitiveValue::Empty`] will create
594    /// an empty data set sequence.
595    Set(PrimitiveValue),
596    /// Fully reset a textual attribute with the given string,
597    /// creating it if it does not exist yet.
598    SetStr(Cow<'static, str>),
599    /// Provide the attribute with the given DICOM value,
600    /// if it does not exist yet.
601    ///
602    /// For objects supporting nested data sets,
603    /// passing [`PrimitiveValue::Empty`] will create
604    /// an empty data set sequence.
605    SetIfMissing(PrimitiveValue),
606    /// Provide the textual attribute with the given string,
607    /// creating it if it does not exist yet.
608    SetStrIfMissing(Cow<'static, str>),
609    /// Fully replace the value with the given DICOM value,
610    /// but only if the attribute already exists.
611    ///
612    /// For objects supporting nested data sets,
613    /// passing [`PrimitiveValue::Empty`] will clear the items
614    /// of an existing data set sequence.
615    Replace(PrimitiveValue),
616    /// Fully replace a textual value with the given string,
617    /// but only if the attribute already exists.
618    ReplaceStr(Cow<'static, str>),
619    /// Append a string as an additional textual value,
620    /// creating the attribute if it does not exist yet.
621    ///
622    /// New value items are recorded as separate text values,
623    /// meaning that they are delimited by a backslash (`\`) at encoding time,
624    /// regardless of the value representation.
625    PushStr(Cow<'static, str>),
626    /// Append a 32-bit signed integer as an additional numeric value,
627    /// creating the attribute if it does not exist yet.
628    PushI32(i32),
629    /// Append a 32-bit unsigned integer as an additional numeric value,
630    /// creating the attribute if it does not exist yet.
631    PushU32(u32),
632    /// Append a 16-bit signed integer as an additional numeric value,
633    /// creating the attribute if it does not exist yet.
634    PushI16(i16),
635    /// Append a 16-bit unsigned integer as an additional numeric value,
636    /// creating the attribute if it does not exist yet.
637    PushU16(u16),
638    /// Append a 32-bit floating point number as an additional numeric value,
639    /// creating the attribute if it does not exist yet.
640    PushF32(f32),
641    /// Append a 64-bit floating point number as an additional numeric value,
642    /// creating the attribute if it does not exist yet.
643    PushF64(f64),
644    /// Truncate a value or a sequence to the given number of items,
645    /// removing extraneous items from the end of the list.
646    ///
647    /// On primitive values, this truncates the value
648    /// by the number of individual value items
649    /// (note that bytes in a [`PrimitiveValue::U8`]
650    /// are treated as individual items).
651    /// On data set sequences and pixel data fragment sequences,
652    /// this operation is applied to
653    /// the data set items (or fragments) in the sequence.
654    ///
655    /// Does nothing if the attribute does not exist
656    /// or the cardinality of the element is already lower than or equal to
657    /// the given size.
658    Truncate(usize),
659}
660
661impl AttributeAction {
662    /// Report whether this is considered a _constructive_ action,
663    /// operations of which create new elements if they do not exist yet.
664    ///
665    /// The actions currently considered to be constructive are
666    /// all actions of the families `Set*`, `SetIfMissing`, and `Push*`.
667    pub fn is_constructive(&self) -> bool {
668        matches!(
669            self,
670            AttributeAction::Set(_)
671                | AttributeAction::SetStr(_)
672                | AttributeAction::SetIfMissing(_)
673                | AttributeAction::SetStrIfMissing(_)
674                | AttributeAction::PushF32(_)
675                | AttributeAction::PushF64(_)
676                | AttributeAction::PushI16(_)
677                | AttributeAction::PushI32(_)
678                | AttributeAction::PushStr(_)
679                | AttributeAction::PushU16(_)
680                | AttributeAction::PushU32(_)
681        )
682    }
683}
684
685/// Trait for applying DICOM attribute operations.
686///
687/// This is typically implemented by DICOM objects and other data set types
688/// to serve as a common API for attribute manipulation.
689pub trait ApplyOp {
690    /// The operation error type
691    type Err: std::error::Error + 'static;
692
693    /// Apply the given attribute operation on the receiving object.
694    ///
695    /// Effects may slightly differ between implementations,
696    /// but should always be compliant with
697    /// the expectations defined in [`AttributeAction`] variants.
698    ///
699    /// If the action to apply is unsupported,
700    /// or not possible for other reasons,
701    /// an error is returned and no changes to the receiver are made.
702    /// While not all kinds of operations may be possible,
703    /// generic DICOM data set holders will usually support all actions.
704    /// See the respective documentation of the implementing type
705    /// for more details.
706    fn apply(&mut self, op: AttributeOp) -> Result<(), Self::Err>;
707}
708
709#[cfg(test)]
710mod tests {
711    use crate::{
712        ops::{AttributeSelector, AttributeSelectorStep},
713        Tag,
714    };
715
716    #[test]
717    fn display_selectors() {
718        let selector: AttributeSelector = Tag(0x0014, 0x5100).into();
719        assert_eq!(selector.to_string(), "(0014,5100)");
720
721        let selector: AttributeSelector = (Tag(0x0018, 0x6011), 2, Tag(0x0018, 0x6012)).into();
722        assert_eq!(selector.to_string(), "(0018,6011)[2].(0018,6012)");
723
724        let selector = AttributeSelector::from((Tag(0x0040, 0xA730), 1, Tag(0x0040, 0xA730)));
725        assert_eq!(selector.to_string(), "(0040,A730)[1].(0040,A730)");
726    }
727
728    #[test]
729    fn split_selectors() {
730        let selector: AttributeSelector = Tag(0x0014, 0x5100).into();
731        assert_eq!(
732            selector.split_first(),
733            (AttributeSelectorStep::Tag(Tag(0x0014, 0x5100)), None)
734        );
735
736        let selector: AttributeSelector = (Tag(0x0018, 0x6011), 2, Tag(0x0018, 0x6012)).into();
737        assert_eq!(
738            selector.split_first(),
739            (
740                AttributeSelectorStep::Nested {
741                    tag: Tag(0x0018, 0x6011),
742                    item: 2
743                },
744                Some(Tag(0x0018, 0x6012).into())
745            )
746        );
747
748        // selector constructor automatically turns the first entry into `Nested`
749        let selector = AttributeSelector::from((Tag(0x0040, 0xA730), Tag(0x0040, 0xA730)));
750        assert_eq!(
751            selector.split_first(),
752            (
753                AttributeSelectorStep::Nested {
754                    tag: Tag(0x0040, 0xA730),
755                    item: 0
756                },
757                Some(Tag(0x0040, 0xA730).into())
758            )
759        );
760    }
761}