Skip to main content

markdown_compiler/
assets.rs

1use std::{num::NonZeroUsize, ops::Range};
2
3use serde::Serialize;
4
5use super::identity::{
6    AssetResolutionPolicyBinding, PostAssetSourceBinding, PostContentDigest,
7    PublicationAssetSourceBinding, bind_asset_resolution_policy,
8    bind_post_asset_source_with_content, bind_publication_asset_source, digest_post_content,
9};
10use super::{AssetRevisionReference, ExternalAssetOrigin, PostDocument, PublicationSettings};
11
12/// The one-based position of a link or image destination in a Markdown event stream.
13#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
14#[serde(transparent)]
15pub struct MarkdownDestinationOrdinal(NonZeroUsize);
16
17impl MarkdownDestinationOrdinal {
18    pub const fn get(self) -> usize {
19        self.0.get()
20    }
21
22    pub const fn new(value: NonZeroUsize) -> Self {
23        Self(value)
24    }
25}
26
27/// A half-open byte range in the exact Markdown source bound to a resolution.
28#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
29pub struct MarkdownSourceRange {
30    pub start: usize,
31    pub end: usize,
32}
33
34impl MarkdownSourceRange {
35    pub fn as_range(self) -> Range<usize> {
36        self.start..self.end
37    }
38
39    pub(super) const fn new(range: Range<usize>) -> Self {
40        Self {
41            start: range.start,
42            end: range.end,
43        }
44    }
45}
46
47#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
48#[serde(rename_all = "snake_case")]
49pub enum MarkdownDestinationKind {
50    Image,
51    Download,
52}
53
54/// The destination value produced by the CommonMark parser for one occurrence.
55#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
56#[serde(transparent)]
57pub struct AuthoredMarkdownDestination(Box<str>);
58
59impl AuthoredMarkdownDestination {
60    pub fn as_str(&self) -> &str {
61        &self.0
62    }
63
64    pub(super) fn new(value: &str) -> Self {
65        Self(value.into())
66    }
67}
68
69/// One resolver-approved Markdown asset destination.
70#[derive(Clone, Debug, Eq, PartialEq)]
71pub struct ResolvedMarkdownDestination {
72    pub ordinal: MarkdownDestinationOrdinal,
73    pub source_range: MarkdownSourceRange,
74    pub kind: MarkdownDestinationKind,
75    pub authored: AuthoredMarkdownDestination,
76    pub target: AssetRevisionReference,
77}
78
79impl ResolvedMarkdownDestination {
80    pub(super) fn new(
81        ordinal: MarkdownDestinationOrdinal,
82        source_range: MarkdownSourceRange,
83        kind: MarkdownDestinationKind,
84        authored: AuthoredMarkdownDestination,
85        target: AssetRevisionReference,
86    ) -> Self {
87        Self {
88            ordinal,
89            source_range,
90            kind,
91            authored,
92            target,
93        }
94    }
95}
96
97/// Resolver-owned, complete asset inputs for one post revision.
98#[derive(Clone, Debug, Eq, PartialEq)]
99pub struct ResolvedPostAssets {
100    pub(super) source_binding: PostAssetSourceBinding,
101    pub(super) policy_binding: AssetResolutionPolicyBinding,
102    pub image: Option<AssetRevisionReference>,
103    pub references: Vec<AssetRevisionReference>,
104    pub markdown_destinations: Vec<ResolvedMarkdownDestination>,
105}
106
107impl ResolvedPostAssets {
108    pub fn new(
109        document: &PostDocument,
110        image: Option<AssetRevisionReference>,
111        references: Vec<AssetRevisionReference>,
112    ) -> Self {
113        Self::from_resolution(
114            document,
115            &digest_post_content(document),
116            &[],
117            image,
118            references,
119            Vec::new(),
120        )
121    }
122
123    pub(super) fn from_resolution(
124        document: &PostDocument,
125        content_digest: &PostContentDigest,
126        allowed_origins: &[ExternalAssetOrigin],
127        image: Option<AssetRevisionReference>,
128        references: Vec<AssetRevisionReference>,
129        markdown_destinations: Vec<ResolvedMarkdownDestination>,
130    ) -> Self {
131        Self {
132            source_binding: bind_post_asset_source_with_content(document, content_digest),
133            policy_binding: bind_asset_resolution_policy(allowed_origins),
134            image,
135            references,
136            markdown_destinations,
137        }
138    }
139
140    pub fn shares_policy_with(&self, site_assets: &ResolvedSiteAssets) -> bool {
141        self.policy_binding == site_assets.policy_binding
142    }
143}
144
145/// Resolver-owned, complete asset inputs for one site snapshot.
146#[derive(Clone, Debug, Eq, PartialEq)]
147pub struct ResolvedSiteAssets {
148    pub(super) source_binding: PublicationAssetSourceBinding,
149    pub(super) policy_binding: AssetResolutionPolicyBinding,
150    pub favicon: Option<AssetRevisionReference>,
151    pub image: Option<AssetRevisionReference>,
152    pub allowed_origins: Vec<ExternalAssetOrigin>,
153    pub references: Vec<AssetRevisionReference>,
154}
155
156impl ResolvedSiteAssets {
157    pub fn new(
158        publication: &PublicationSettings,
159        favicon: Option<AssetRevisionReference>,
160        image: Option<AssetRevisionReference>,
161        allowed_origins: Vec<ExternalAssetOrigin>,
162        references: Vec<AssetRevisionReference>,
163    ) -> Self {
164        Self {
165            source_binding: bind_publication_asset_source(publication),
166            policy_binding: bind_asset_resolution_policy(&allowed_origins),
167            favicon,
168            image,
169            allowed_origins,
170            references,
171        }
172    }
173}