ephemeris_documents/
spec.rs1mod structured;
4mod text;
5
6use facet::Facet;
7
8pub use self::structured::StructuredSpec;
9pub use self::text::TextSpec;
10use crate::error::DocumentError;
11use crate::format::Format;
12use crate::line_ending::LineEndingSpec;
13use crate::markdown::MarkdownSpec;
14use crate::ownership::{DocumentOwnership, ManagedPath};
15use crate::source::Source;
16
17#[derive(Debug, Clone, Facet)]
19#[facet(tag = "type", deny_unknown_fields)]
20#[repr(u8)]
21pub enum DocumentSpec {
22 #[facet(rename = "text")]
24 Text(TextSpec),
25 #[facet(rename = "structured")]
27 Structured(StructuredSpec),
28 #[facet(rename = "markdown")]
30 Markdown(MarkdownSpec),
31}
32
33pub(crate) trait Payload {
35 fn line_ending(&self) -> LineEndingSpec;
37
38 fn reconcile(&self, source: &Source<'_>) -> Result<Vec<u8>, DocumentError>;
44
45 fn validate(&self) -> Result<(), DocumentError>;
51}
52
53impl DocumentSpec {
54 #[must_use]
58 pub fn set_string(format: Format, key: &ManagedPath, value: &str) -> Self {
59 Self::Structured(StructuredSpec {
60 format,
61 value: key.nest(value.into()),
62 ownership: DocumentOwnership::selected([key.clone()]),
63 line_ending: LineEndingSpec::Preserve,
64 })
65 }
66
67 fn payload(&self) -> &dyn Payload {
68 match self {
69 Self::Text(spec) => spec,
70 Self::Structured(spec) => spec,
71 Self::Markdown(spec) => spec,
72 }
73 }
74
75 #[must_use]
77 pub const fn is_partial(&self) -> bool {
78 match self {
79 Self::Text(_) => false,
80 Self::Structured(spec) => spec.is_partial(),
81 Self::Markdown(spec) => spec.is_partial(),
82 }
83 }
84
85 pub fn validate(&self) -> Result<(), DocumentError> {
91 self.payload().validate()
92 }
93
94 pub fn reconcile(&self, current: Option<&[u8]>) -> Result<Vec<u8>, DocumentError> {
101 let payload = self.payload();
102 let source = Source::read(current, payload.line_ending(), self.is_partial())?;
103 payload.reconcile(&source)
104 }
105}
106
107#[cfg(test)]
108mod tests;