1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
//! SAM record data field tag.

mod other;
mod standard;

use std::{borrow::Borrow, error, fmt, str::FromStr};

use self::{other::Other, standard::Standard};

/// The smallest template-independent mapping quality in the template (`AM`).
pub const MIN_MAPPING_QUALITY: Tag = Tag::Standard(Standard::MinMappingQuality);

/// Alignment score generated by aligner (`AS`).
pub const ALIGNMENT_SCORE: Tag = Tag::Standard(Standard::AlignmentScore);

/// Barcode sequence identifying the sample (`BC`).
pub const SAMPLE_BARCODE_SEQUENCE: Tag = Tag::Standard(Standard::SampleBarcodeSequence);

/// Offset to base alignment quality (BAQ) (`BQ`).
pub const BASE_ALIGNMENT_QUALITY_OFFSETS: Tag =
    Tag::Standard(Standard::BaseAlignmentQualityOffsets);

/// Phred quality of the unique molecular barcode bases in the `OX` tag (`BZ`).
pub const ORIGINAL_UMI_QUALITY_SCORES: Tag = Tag::Standard(Standard::OriginalUmiQualityScores);

/// Cell identifier (`CB`).
pub const CELL_BARCODE_ID: Tag = Tag::Standard(Standard::CellBarcodeId);

/// Reference name of the next hit (`CC`).
pub const NEXT_HIT_REFERENCE_SEQUENCE_NAME: Tag =
    Tag::Standard(Standard::NextHitReferenceSequenceName);

/// BAM only: `CIGAR` in BAM's binary encoding if (and only if) it consists of > 65535 operators
/// (`CG`).
pub const CIGAR: Tag = Tag::Standard(Standard::Cigar);

/// Edit distance between the color sequence and the color reference (see also `NM`) (`CM`).
pub const COLOR_EDIT_DISTANCE: Tag = Tag::Standard(Standard::ColorEditDistance);

/// Free-text comments (`CO`).
pub const COMMENT: Tag = Tag::Standard(Standard::Comment);

/// Leftmost coordinate of the next hit (`CP`).
pub const NEXT_HIT_POSITION: Tag = Tag::Standard(Standard::NextHitPosition);

/// Color read base qualities (`CQ`).
pub const COLOR_QUALITY_SCORES: Tag = Tag::Standard(Standard::ColarQualityScores);

/// Cellular barcode sequence bases (uncorrected) (`CR`).
pub const CELL_BARCODE_SEQUENCE: Tag = Tag::Standard(Standard::CellBarcodeSequence);

/// Color read sequence (`CS`).
pub const COLOR_SEQUENCE: Tag = Tag::Standard(Standard::ColorSequence);

/// Complete read annotation tag, used for consensus annotation dummy features (`CT`).
pub const COMPLETE_READ_ANNOTATIONS: Tag = Tag::Standard(Standard::CompleteReadAnnotations);

/// Phred quality of the cellular barcode sequence in the `CR` tag (`CY`).
pub const CELL_BARCODE_QUALITY_SCORES: Tag = Tag::Standard(Standard::CellBarcodeQualityScores);

/// The 2nd most likely base calls (`E2`).
pub const NEXT_HIT_SEQUENCE: Tag = Tag::Standard(Standard::NextHitSequence);

/// The index of segment in the template (`FI`).
pub const SEGMENT_INDEX: Tag = Tag::Standard(Standard::SegmentIndex);

/// Segment suffix (`FS`).
pub const SEGMENT_SUFFIX: Tag = Tag::Standard(Standard::SegmentSuffix);

/// Flow signal intensities (`FZ`).
pub const ALTERNATIVE_SEQUENCE: Tag = Tag::Standard(Standard::AlternativeSequence);

/// Reserved for backwards compatibility reasons (`GC`).
pub const RESERVED_GC: Tag = Tag::Standard(Standard::ReservedGc);

/// Reserved for backwards compatibility reasons (`GQ`).
pub const RESERVED_GQ: Tag = Tag::Standard(Standard::ReservedGq);

/// Reserved for backwards compatibility reasons (`GS`).
pub const RESERVED_GS: Tag = Tag::Standard(Standard::ReservedGs);

/// Number of perfect hits (`H0`).
pub const PERFECT_HIT_COUNT: Tag = Tag::Standard(Standard::PerfectHitCount);

/// Number of 1-difference hits (see also `NM`) (`H1`).
pub const ONE_DIFFERENCE_HIT_COUNT: Tag = Tag::Standard(Standard::OneDifferenceHitCount);

/// Number of 2-difference hits (`H2`).
pub const TWO_DIFFERENCE_HIT_COUNT: Tag = Tag::Standard(Standard::TwoDifferenceHitCount);

/// Query hit index (`HI`).
pub const HIT_INDEX: Tag = Tag::Standard(Standard::HitIndex);

/// Query hit total count (`IH`).
pub const TOTAL_HIT_COUNT: Tag = Tag::Standard(Standard::TotalHitCount);

/// Library (`LB`).
pub const LIBRARY: Tag = Tag::Standard(Standard::Library);

/// CIGAR string for mate/next segment (`MC`).
pub const MATE_CIGAR: Tag = Tag::Standard(Standard::MateCigar);

/// String encoding mismatched and deleted reference bases (`MD`).
pub const MISMATCHED_POSITIONS: Tag = Tag::Standard(Standard::MismatchedPositions);

/// Reserved for backwards compatibility reasons (`MF`).
pub const RESERVED_MF: Tag = Tag::Standard(Standard::ReservedMf);

/// Molecular identifier (`MI`).
///
/// A string that uniquely identifies the molecule from which the record was derived.
pub const UMI_ID: Tag = Tag::Standard(Standard::UmiId);

/// Base modification probabilities (`ML`).
pub const BASE_MODIFICATION_PROBABILITIES: Tag =
    Tag::Standard(Standard::BaseModificationProbabilities);

/// Base modifications / methylation (`MM`).
pub const BASE_MODIFICATIONS: Tag = Tag::Standard(Standard::BaseModifications);

/// Length of sequence at the time `MM` and `ML` were produced (`MN`).
pub const BASE_MODIFICATION_SEQUENCE_LENGTH: Tag =
    Tag::Standard(Standard::BaseModificationSequenceLength);

/// Mapping quality of the mate/next segment (`MQ`).
pub const MATE_MAPPING_QUALITY: Tag = Tag::Standard(Standard::MateMappingQuality);

/// Number of reported alignments that contain the query in the current record (`NH`).
pub const ALIGNMENT_HIT_COUNT: Tag = Tag::Standard(Standard::AlignmentHitCount);

/// Edit distance to the reference (`NM`).
pub const EDIT_DISTANCE: Tag = Tag::Standard(Standard::EditDistance);

/// Original alignment (`OA`).
pub const ORIGINAL_ALIGNMENT: Tag = Tag::Standard(Standard::OriginalAlignment);

/// Original CIGAR (deprecated; use `OA` instead) (`OC`).
pub const ORIGINAL_CIGAR: Tag = Tag::Standard(Standard::OriginalCigar);

/// Original mapping position (deprecated; use `OA` instead) (`OP`).
pub const ORIGINAL_POSITION: Tag = Tag::Standard(Standard::OriginalPosition);

/// Original base quality (`OQ`).
pub const ORIGINAL_QUALITY_SCORES: Tag = Tag::Standard(Standard::OriginalQualityScores);

/// Original unique molecular barcode bases (`OX`).
pub const ORIGINAL_UMI_BARCODE_SEQUENCE: Tag = Tag::Standard(Standard::OriginalUmiBarcodeSequence);

/// Program (`PG`).
pub const PROGRAM: Tag = Tag::Standard(Standard::Program);

/// Phred likelihood of the template (`PQ`).
pub const TEMPLATE_LIKELIHOOD: Tag = Tag::Standard(Standard::TemplateLikelihood);

/// Read annotations for parse of the padded read sequence (`PT`).
pub const PADDED_READ_ANNOTATIONS: Tag = Tag::Standard(Standard::PaddedReadAnnotations);

/// Platform unit (`PU`).
pub const PLATFORM_UNIT: Tag = Tag::Standard(Standard::PlatformUnit);

/// Phred quality of the mate/next segment sequence in the `R2` tag (`Q2`).
pub const MATE_QUALITY_SCORES: Tag = Tag::Standard(Standard::MateQualityScores);

/// Phred quality of the sample barcode sequence in the `BC` tag (`QT`).
pub const SAMPLE_BARCODE_QUALITY_SCORES: Tag = Tag::Standard(Standard::SampleBarcodeQualityScores);

/// Quality score of the unique molecular identifier in the `RX` tag (`QX`).
pub const UMI_QUALITY_SCORES: Tag = Tag::Standard(Standard::UmiQualityScores);

/// Sequence of the mate/next segment in the template (`R2`).
pub const MATE_SEQUENCE: Tag = Tag::Standard(Standard::MateSequence);

/// Read group (`RG`).
pub const READ_GROUP: Tag = Tag::Standard(Standard::ReadGroup);

/// Reserved for backwards compatibility reasons (`RT`).
pub const RESERVED_RT: Tag = Tag::Standard(Standard::ReservedRt);

/// Sequence bases of the (possibly corrected) unique molecular identifier (`RX`).
pub const UMI_SEQUENCE: Tag = Tag::Standard(Standard::UmiSequence);

/// Reserved for backwards compatibility reasons (`S2`).
pub const RESERVED_S2: Tag = Tag::Standard(Standard::ReservedS2);

/// Other canonical alignments in a chimeric alignment (`SA`).
pub const OTHER_ALIGNMENTS: Tag = Tag::Standard(Standard::OtherAlignments);

/// Template-independent mapping quality (`SM`).
pub const TEMPLATE_MAPPING_QUALITY: Tag = Tag::Standard(Standard::TemplateMappingQuality);

/// Reserved for backwards compatibility reasons (`SQ`).
pub const RESERVED_SQ: Tag = Tag::Standard(Standard::ReservedSq);

/// The number of segments in the template (`TC`).
pub const SEGMENT_COUNT: Tag = Tag::Standard(Standard::SegmentCount);

/// Transcript strand (`TS`).
pub const TRANSCRIPT_STRAND: Tag = Tag::Standard(Standard::TranscriptStrand);

/// Phred probability of the 2nd call being wrong conditional on the best being wrong (`U2`).
pub const NEXT_HIT_QUALITY_SCORES: Tag = Tag::Standard(Standard::NextHitQualityScores);

/// Phred likelihood of the segment, conditional on the mapping being correct (`UQ`).
pub const SEGMENT_LIKELIHOOD: Tag = Tag::Standard(Standard::SegmentLikelihood);

const LENGTH: usize = 2;

/// A SAM record data field tag.
///
/// Standard tags are defined in "Sequence Alignment/Map Optional Fields Specification"
/// (2020-05-29).
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Tag {
    /// A standard tag.
    Standard(Standard),
    /// A non-standard tag.
    Other(Other),
}

impl AsRef<[u8; LENGTH]> for Tag {
    fn as_ref(&self) -> &[u8; LENGTH] {
        match self {
            Self::Standard(tag) => tag.as_ref(),
            Self::Other(Other(tag)) => tag,
        }
    }
}

impl Borrow<[u8; LENGTH]> for Tag {
    fn borrow(&self) -> &[u8; LENGTH] {
        self.as_ref()
    }
}

impl fmt::Display for Tag {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let [b0, b1] = self.as_ref();
        char::from(*b0).fmt(f)?;
        char::from(*b1).fmt(f)?;
        Ok(())
    }
}

/// An error returned when a raw SAM record data field tag fails to parse.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ParseError {
    /// The input is empty.
    Empty,
    /// The length is invalid.
    ///
    /// The tag length must be 2 characters.
    InvalidLength(usize),
    /// A character is invalid.
    InvalidCharacter(char),
}

impl error::Error for ParseError {}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Empty => write!(f, "empty input"),
            Self::InvalidLength(len) => {
                write!(f, "invalid length: expected {LENGTH}, got {len}")
            }
            Self::InvalidCharacter(c) => write!(f, "invalid character: {c}"),
        }
    }
}

// § 1.5 The alignment section: optional fields (2021-01-07)
impl TryFrom<[u8; LENGTH]> for Tag {
    type Error = ParseError;

    fn try_from(b: [u8; LENGTH]) -> Result<Self, Self::Error> {
        match Standard::try_from(b) {
            Ok(tag) => Ok(Self::Standard(tag)),
            Err(_) => {
                if !b[0].is_ascii_alphabetic() {
                    Err(ParseError::InvalidCharacter(char::from(b[0])))
                } else if !b[1].is_ascii_alphanumeric() {
                    Err(ParseError::InvalidCharacter(char::from(b[1])))
                } else {
                    Ok(Self::Other(Other(b)))
                }
            }
        }
    }
}

impl TryFrom<&[u8]> for Tag {
    type Error = ParseError;

    fn try_from(b: &[u8]) -> Result<Self, Self::Error> {
        if b.is_empty() {
            Err(ParseError::Empty)
        } else if b.len() == LENGTH {
            Self::try_from([b[0], b[1]])
        } else {
            Err(ParseError::InvalidLength(b.len()))
        }
    }
}

impl FromStr for Tag {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Tag::try_from(s.as_bytes())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_fmt() {
        assert_eq!(MIN_MAPPING_QUALITY.to_string(), "AM");
        assert_eq!(ALIGNMENT_SCORE.to_string(), "AS");
        assert_eq!(SAMPLE_BARCODE_SEQUENCE.to_string(), "BC");
        assert_eq!(BASE_ALIGNMENT_QUALITY_OFFSETS.to_string(), "BQ");
        assert_eq!(ORIGINAL_UMI_QUALITY_SCORES.to_string(), "BZ");
        assert_eq!(CELL_BARCODE_ID.to_string(), "CB");
        assert_eq!(NEXT_HIT_REFERENCE_SEQUENCE_NAME.to_string(), "CC");
        assert_eq!(CIGAR.to_string(), "CG");
        assert_eq!(COLOR_EDIT_DISTANCE.to_string(), "CM");
        assert_eq!(COMMENT.to_string(), "CO");
        assert_eq!(NEXT_HIT_POSITION.to_string(), "CP");
        assert_eq!(COLOR_QUALITY_SCORES.to_string(), "CQ");
        assert_eq!(CELL_BARCODE_SEQUENCE.to_string(), "CR");
        assert_eq!(COLOR_SEQUENCE.to_string(), "CS");
        assert_eq!(COMPLETE_READ_ANNOTATIONS.to_string(), "CT");
        assert_eq!(CELL_BARCODE_QUALITY_SCORES.to_string(), "CY");
        assert_eq!(NEXT_HIT_SEQUENCE.to_string(), "E2");
        assert_eq!(SEGMENT_INDEX.to_string(), "FI");
        assert_eq!(SEGMENT_SUFFIX.to_string(), "FS");
        assert_eq!(ALTERNATIVE_SEQUENCE.to_string(), "FZ");
        assert_eq!(RESERVED_GC.to_string(), "GC");
        assert_eq!(RESERVED_GQ.to_string(), "GQ");
        assert_eq!(RESERVED_GS.to_string(), "GS");
        assert_eq!(PERFECT_HIT_COUNT.to_string(), "HO");
        assert_eq!(ONE_DIFFERENCE_HIT_COUNT.to_string(), "H1");
        assert_eq!(TWO_DIFFERENCE_HIT_COUNT.to_string(), "H2");
        assert_eq!(HIT_INDEX.to_string(), "HI");
        assert_eq!(TOTAL_HIT_COUNT.to_string(), "IH");
        assert_eq!(LIBRARY.to_string(), "LB");
        assert_eq!(MATE_CIGAR.to_string(), "MC");
        assert_eq!(MISMATCHED_POSITIONS.to_string(), "MD");
        assert_eq!(RESERVED_MF.to_string(), "MF");
        assert_eq!(UMI_ID.to_string(), "MI");
        assert_eq!(BASE_MODIFICATION_PROBABILITIES.to_string(), "ML");
        assert_eq!(BASE_MODIFICATIONS.to_string(), "MM");
        assert_eq!(MATE_MAPPING_QUALITY.to_string(), "MQ");
        assert_eq!(ALIGNMENT_HIT_COUNT.to_string(), "NH");
        assert_eq!(EDIT_DISTANCE.to_string(), "NM");
        assert_eq!(ORIGINAL_ALIGNMENT.to_string(), "OA");
        assert_eq!(ORIGINAL_CIGAR.to_string(), "OC");
        assert_eq!(ORIGINAL_POSITION.to_string(), "OP");
        assert_eq!(ORIGINAL_QUALITY_SCORES.to_string(), "OQ");
        assert_eq!(ORIGINAL_UMI_BARCODE_SEQUENCE.to_string(), "OX");
        assert_eq!(PROGRAM.to_string(), "PG");
        assert_eq!(TEMPLATE_LIKELIHOOD.to_string(), "PQ");
        assert_eq!(PADDED_READ_ANNOTATIONS.to_string(), "PT");
        assert_eq!(PLATFORM_UNIT.to_string(), "PU");
        assert_eq!(MATE_QUALITY_SCORES.to_string(), "Q2");
        assert_eq!(SAMPLE_BARCODE_QUALITY_SCORES.to_string(), "QT");
        assert_eq!(UMI_QUALITY_SCORES.to_string(), "QX");
        assert_eq!(MATE_SEQUENCE.to_string(), "R2");
        assert_eq!(READ_GROUP.to_string(), "RG");
        assert_eq!(RESERVED_RT.to_string(), "RT");
        assert_eq!(UMI_SEQUENCE.to_string(), "RX");
        assert_eq!(RESERVED_S2.to_string(), "S2");
        assert_eq!(OTHER_ALIGNMENTS.to_string(), "SA");
        assert_eq!(TEMPLATE_MAPPING_QUALITY.to_string(), "SM");
        assert_eq!(RESERVED_SQ.to_string(), "SQ");
        assert_eq!(SEGMENT_COUNT.to_string(), "TC");
        assert_eq!(TRANSCRIPT_STRAND.to_string(), "TS");
        assert_eq!(NEXT_HIT_QUALITY_SCORES.to_string(), "U2");
        assert_eq!(SEGMENT_LIKELIHOOD.to_string(), "UQ");
        assert_eq!(Tag::Other(Other([b'Z', b'N'])).to_string(), "ZN");
    }

    #[test]
    fn test_from_str() {
        assert_eq!("AM".parse(), Ok(MIN_MAPPING_QUALITY));
        assert_eq!("AS".parse(), Ok(ALIGNMENT_SCORE));
        assert_eq!("BC".parse(), Ok(SAMPLE_BARCODE_SEQUENCE));
        assert_eq!("BQ".parse(), Ok(BASE_ALIGNMENT_QUALITY_OFFSETS));
        assert_eq!("BZ".parse(), Ok(ORIGINAL_UMI_QUALITY_SCORES));
        assert_eq!("CB".parse(), Ok(CELL_BARCODE_ID));
        assert_eq!("CC".parse(), Ok(NEXT_HIT_REFERENCE_SEQUENCE_NAME));
        assert_eq!("CG".parse(), Ok(CIGAR));
        assert_eq!("CM".parse(), Ok(COLOR_EDIT_DISTANCE));
        assert_eq!("CO".parse(), Ok(COMMENT));
        assert_eq!("CP".parse(), Ok(NEXT_HIT_POSITION));
        assert_eq!("CQ".parse(), Ok(COLOR_QUALITY_SCORES));
        assert_eq!("CR".parse(), Ok(CELL_BARCODE_SEQUENCE));
        assert_eq!("CS".parse(), Ok(COLOR_SEQUENCE));
        assert_eq!("CT".parse(), Ok(COMPLETE_READ_ANNOTATIONS));
        assert_eq!("CY".parse(), Ok(CELL_BARCODE_QUALITY_SCORES));
        assert_eq!("E2".parse(), Ok(NEXT_HIT_SEQUENCE));
        assert_eq!("FI".parse(), Ok(SEGMENT_INDEX));
        assert_eq!("FS".parse(), Ok(SEGMENT_SUFFIX));
        assert_eq!("FZ".parse(), Ok(ALTERNATIVE_SEQUENCE));
        assert_eq!("GC".parse(), Ok(RESERVED_GC));
        assert_eq!("GQ".parse(), Ok(RESERVED_GQ));
        assert_eq!("GS".parse(), Ok(RESERVED_GS));
        assert_eq!("HO".parse(), Ok(PERFECT_HIT_COUNT));
        assert_eq!("H1".parse(), Ok(ONE_DIFFERENCE_HIT_COUNT));
        assert_eq!("H2".parse(), Ok(TWO_DIFFERENCE_HIT_COUNT));
        assert_eq!("HI".parse(), Ok(HIT_INDEX));
        assert_eq!("IH".parse(), Ok(TOTAL_HIT_COUNT));
        assert_eq!("LB".parse(), Ok(LIBRARY));
        assert_eq!("MC".parse(), Ok(MATE_CIGAR));
        assert_eq!("MD".parse(), Ok(MISMATCHED_POSITIONS));
        assert_eq!("MF".parse(), Ok(RESERVED_MF));
        assert_eq!("MI".parse(), Ok(UMI_ID));
        assert_eq!("ML".parse(), Ok(BASE_MODIFICATION_PROBABILITIES));
        assert_eq!("MM".parse(), Ok(BASE_MODIFICATIONS));
        assert_eq!("MN".parse(), Ok(BASE_MODIFICATION_SEQUENCE_LENGTH));
        assert_eq!("MQ".parse(), Ok(MATE_MAPPING_QUALITY));
        assert_eq!("NH".parse(), Ok(ALIGNMENT_HIT_COUNT));
        assert_eq!("NM".parse(), Ok(EDIT_DISTANCE));
        assert_eq!("OA".parse(), Ok(ORIGINAL_ALIGNMENT));
        assert_eq!("OC".parse(), Ok(ORIGINAL_CIGAR));
        assert_eq!("OP".parse(), Ok(ORIGINAL_POSITION));
        assert_eq!("OQ".parse(), Ok(ORIGINAL_QUALITY_SCORES));
        assert_eq!("OX".parse(), Ok(ORIGINAL_UMI_BARCODE_SEQUENCE));
        assert_eq!("PG".parse(), Ok(PROGRAM));
        assert_eq!("PQ".parse(), Ok(TEMPLATE_LIKELIHOOD));
        assert_eq!("PT".parse(), Ok(PADDED_READ_ANNOTATIONS));
        assert_eq!("PU".parse(), Ok(PLATFORM_UNIT));
        assert_eq!("Q2".parse(), Ok(MATE_QUALITY_SCORES));
        assert_eq!("QT".parse(), Ok(SAMPLE_BARCODE_QUALITY_SCORES));
        assert_eq!("QX".parse(), Ok(UMI_QUALITY_SCORES));
        assert_eq!("R2".parse(), Ok(MATE_SEQUENCE));
        assert_eq!("RG".parse(), Ok(READ_GROUP));
        assert_eq!("RT".parse(), Ok(RESERVED_RT));
        assert_eq!("RX".parse(), Ok(UMI_SEQUENCE));
        assert_eq!("S2".parse(), Ok(RESERVED_S2));
        assert_eq!("SA".parse(), Ok(OTHER_ALIGNMENTS));
        assert_eq!("SM".parse(), Ok(TEMPLATE_MAPPING_QUALITY));
        assert_eq!("SQ".parse(), Ok(RESERVED_SQ));
        assert_eq!("TC".parse(), Ok(SEGMENT_COUNT));
        assert_eq!("TS".parse(), Ok(TRANSCRIPT_STRAND));
        assert_eq!("U2".parse(), Ok(NEXT_HIT_QUALITY_SCORES));
        assert_eq!("UQ".parse(), Ok(SEGMENT_LIKELIHOOD));
        assert_eq!("ZN".parse(), Ok(Tag::Other(Other([b'Z', b'N']))));

        assert_eq!("".parse::<Tag>(), Err(ParseError::Empty));
        assert_eq!("R".parse::<Tag>(), Err(ParseError::InvalidLength(1)));
        assert_eq!("RGP".parse::<Tag>(), Err(ParseError::InvalidLength(3)));
        assert_eq!("1G".parse::<Tag>(), Err(ParseError::InvalidCharacter('1')));
        assert_eq!("R_".parse::<Tag>(), Err(ParseError::InvalidCharacter('_')));
    }
}