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}