Skip to main content

WritableCustomTag

Trait WritableCustomTag 

Source
pub trait WritableCustomTag<'a>: CustomTag<'a> {
    // Required method
    fn into_writable_tag(self) -> WritableTag<'a>;
}
Expand description

A custom tag implementation that allows for writing using crate::Writer.

If there is no intention to write the parsed data then this trait does not need to be implemented for the CustomTag. We can extend the examples from CustomTag to also implement this trait so that we can demonstrate writing the data to an output.

§Single tag example

Recall that the single tag example was for the custom defined #EXT-X-JOKE tag. Here we show how we may change the joke (e.g. if we are acting as a proxy) before writing to output. Note, in a real implementation we would make the stored property a std::borrow::Cow and not require the user to provide a string slice reference with the same lifetime as the parsed data, but this is just extending an existing example for information purposes.

impl JokeType {
    fn as_str(self) -> &'static str {
        match self {
            JokeType::Dad => "DAD",
            JokeType::Pun => "PUN",
            JokeType::Bar => "BAR",
            JokeType::Story => "STORY",
            JokeType::KnockKnock => "KNOCK-KNOCK",
        }
    }
}
impl<'a> JokeTag<'a> {
    fn set_joke(&mut self, joke: &'static str) {
        self.joke = joke;
    }
}
impl<'a> WritableCustomTag<'a> for JokeTag<'a> {
    fn into_writable_tag(self) -> WritableTag<'a> {
        // Note, that the `WritableTag` expects to have `name: Cow<'a, str>` and
        // `value: WritableTagValue<'a>`; however, the `new` method accepts
        // `impl Into<Cow<'a, str>>` for name, and `impl Into<WritableTagValue<'a>>` for value.
        // The library provides convenience `From<T>` implementations for many types of `T` to
        // `WritableTagValue`, so this may help in some cases with shortening how much needs to
        // be written. Below we make use of `From<[(K, V); N]>` where `const N: usize`,
        // `K: Into<Cow<'a, str>>`, and `V: Into<WritableAttributeValue>`.
        WritableTag::new(
            "-X-JOKE",
            [
                (
                    "TYPE",
                    WritableAttributeValue::UnquotedString(self.joke_type.as_str().into()),
                ),
                (
                    "JOKE",
                    WritableAttributeValue::QuotedString(self.joke.into()),
                ),
            ],
        )
    }
}
let mut writer = Writer::new(Vec::new());
// First 3 tags as expected
let Some(m3u) = reader.read_line()? else { return Ok(()) };
writer.write_custom_line(m3u)?;
let Some(targetduration) = reader.read_line()? else { return Ok(()) };
writer.write_custom_line(targetduration)?;
let Some(version) = reader.read_line()? else { return Ok(()) };
writer.write_custom_line(version)?;
// And the big reveal
match reader.read_line() {
    Ok(Some(HlsLine::KnownTag(KnownTag::Custom(mut tag)))) => {
        tag.as_mut().set_joke("What happens when a frog's car breaks down? It gets toad!");
        writer.write_custom_line(HlsLine::from(tag))?;
    }
    r => panic!("unexpected result {r:?}"),
}

// Because the HashMap we return does not guarantee order of the attributes, we validate that
// the result is one of the expected outcomes.
const EXPECTED_1: &str = r#"#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-JOKE:TYPE=DAD,JOKE="What happens when a frog's car breaks down? It gets toad!"
"#;
const EXPECTED_2: &str = r#"#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-JOKE:JOKE="What happens when a frog's car breaks down? It gets toad!",TYPE=DAD
"#;
let inner_bytes = writer.into_inner();
let actual = std::str::from_utf8(&inner_bytes)?;
assert!(actual == EXPECTED_1 || actual == EXPECTED_2);

§Multiple tag example

Recall that the multiple tag example was for the LHLS extension to the specification. Here we show how we may change the prefetch URL (e.g. if we are acting as a proxy) before writing to output.

#[derive(Debug, PartialEq, Clone)]
impl<'a> WritableCustomTag<'a> for LHlsTag<'a> {
    fn into_writable_tag(self) -> WritableTag<'a> {
        // Note, as mentioned above, the `WritableTag::new` method accepts types that implement
        // `Into` the stored properties on the struct. Below we make use of `From<&str>` for
        // `WritableTagValue` in the `Prefetch` case to cut down on boilerplate.
        match self {
            Self::Discontinuity => WritableTag::new(
                "-X-PREFETCH-DISCONTINUITY",
                WritableTagValue::Empty
            ),
            Self::Prefetch(uri) => WritableTag::new("-X-PREFETCH", uri),
        }
    }
}

let mut writer = Writer::new(Vec::new());
let mut last_segment_index = 0;
loop {
    match reader.read_line() {
        Ok(Some(HlsLine::KnownTag(KnownTag::Custom(tag)))) => {
            match tag.as_ref() {
                LHlsTag::Discontinuity => {
                    writer.write_custom_line(HlsLine::from(tag))?;
                }
                LHlsTag::Prefetch(uri) => {
                    // For demo purposes we make the URI segment numbers sequential.
                    if let Some(last_component) = uri.split('/').last() {
                        let new_uri = uri.replace(
                            last_component,
                            format!("{}.ts", last_segment_index + 1).as_str()
                        );
                        writer.write_custom_tag(LHlsTag::Prefetch(new_uri.as_str()))?;
                    } else {
                        writer.write_custom_line(HlsLine::from(tag))?;
                    }
                }
            };
        }
        Ok(Some(HlsLine::Uri(uri))) => {
            last_segment_index = uri
                .split('/')
                .last()
                .and_then(|file| file.split('.').next())
                .and_then(|n| n.parse::<u32>().ok())
                .unwrap_or_default();
            writer.write_line(HlsLine::Uri(uri))?;
        }
        Ok(Some(line)) => {
            writer.write_custom_line(line)?;
        }
        Ok(None) => break,
        Err(e) => {
            writer.get_mut().write_all(e.errored_line.as_bytes())?;
        }
    }
}
const EXPECTED: &str = r#"#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-DISCONTINUITY-SEQUENCE:0

#EXT-X-PROGRAM-DATE-TIME:2018-09-05T20:59:06.531Z
#EXTINF:2.000
https://foo.com/bar/0.ts
#EXT-X-PROGRAM-DATE-TIME:2018-09-05T20:59:08.531Z
#EXTINF:2.000
https://foo.com/bar/1.ts

#EXT-X-PREFETCH-DISCONTINUITY
#EXT-X-PREFETCH:https://foo.com/bar/2.ts
#EXT-X-PREFETCH:https://foo.com/bar/3.ts
"#;

Required Methods§

Source

fn into_writable_tag(self) -> WritableTag<'a>

Takes ownership of the custom tag and provides a value that is used for writing.

This method is only called if there was a mutable borrow of the custom tag at some stage. If the tag was never mutably borrowed, then when writing, the library will use the original input data (thus avoiding unnecessary allocations).

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§