ephemeris_documents/
format.rs1use std::fmt;
5
6use facet::Facet;
7
8use crate::annotation::Annotated;
9use crate::backend::Backend;
10use crate::columns::Columns;
11use crate::error::{ColumnsError, DocumentError, Feature, ValueError};
12use crate::json::{Dialect, Json};
13use crate::ownership::{ManagedPath, Ownership};
14use crate::source::Source;
15use crate::toml::Toml;
16use crate::yaml::{LiteralBlock, Yaml};
17
18#[derive(Debug, Clone, Copy, Facet, PartialEq, Eq)]
20#[facet(tag = "type", deny_unknown_fields)]
21#[repr(u8)]
22pub enum Format {
23 #[facet(rename = "json")]
25 Json,
26 #[facet(rename = "json5")]
28 Json5,
29 #[facet(rename = "toml")]
31 Toml {
32 inline_width: InlineWidth,
34 },
35 #[facet(rename = "yaml")]
37 Yaml,
38}
39
40trait Run {
42 type Output;
43
44 fn run(self, backend: &impl Backend) -> Self::Output;
45}
46
47struct Reconcile<'a> {
48 desired: &'a Annotated,
49 source: &'a Source<'a>,
50 ownership: &'a Ownership,
51}
52
53impl Run for Reconcile<'_> {
54 type Output = Result<Vec<u8>, DocumentError>;
55
56 fn run(self, backend: &impl Backend) -> Self::Output {
57 backend.reconcile(self.desired, self.source, self.ownership)
58 }
59}
60
61struct ReadString<'a> {
62 source: &'a str,
63 path: &'a [String],
64}
65
66impl Run for ReadString<'_> {
67 type Output = Result<Option<String>, DocumentError>;
68
69 fn run(self, backend: &impl Backend) -> Self::Output {
70 backend.read_string(self.source, self.path)
71 }
72}
73
74impl Format {
75 #[must_use]
78 pub fn from_extension(extension: &str, inline_width: InlineWidth) -> Option<Self> {
79 match extension {
80 "toml" => Some(Self::Toml { inline_width }),
81 "yaml" | "yml" => Some(Self::Yaml),
82 "json" => Some(Self::Json),
83 "json5" => Some(Self::Json5),
84 _ => None,
85 }
86 }
87
88 fn run<R: Run>(self, operation: R) -> R::Output {
89 match self {
90 Self::Toml { inline_width } => operation.run(&Toml::new(inline_width)),
91 Self::Yaml => operation.run(&Yaml),
92 Self::Json => operation.run(&Json::new(Dialect::Json)),
93 Self::Json5 => operation.run(&Json::new(Dialect::Json5)),
94 }
95 }
96
97 pub(crate) fn reconcile(
103 self,
104 desired: &Annotated,
105 source: &Source<'_>,
106 ownership: &Ownership,
107 ) -> Result<Vec<u8>, DocumentError> {
108 self.run(Reconcile {
109 desired,
110 source,
111 ownership,
112 })
113 }
114
115 pub fn read_string(
123 self,
124 source: &str,
125 path: &ManagedPath,
126 ) -> Result<Option<String>, DocumentError> {
127 self.run(ReadString {
128 source,
129 path: path.segments(),
130 })
131 }
132
133 pub(crate) const fn supports(self, feature: Feature) -> bool {
138 match feature {
139 Feature::Null | Feature::LargeInteger => {
140 !matches!(self, Self::Toml { inline_width: _ })
141 }
142 Feature::InlineComment => matches!(self, Self::Json5 | Self::Yaml),
143 Feature::DottedTable => matches!(self, Self::Toml { inline_width: _ }),
144 Feature::Comment | Feature::LiteralString => !matches!(self, Self::Json),
145 }
146 }
147
148 pub(crate) fn reproduces_literal(self, text: &str) -> bool {
150 match self {
151 Self::Yaml => LiteralBlock::new(text).is_representable(),
152 Self::Json | Self::Json5 | Self::Toml { inline_width: _ } => true,
153 }
154 }
155
156 pub(crate) const fn reject(self, feature: Feature) -> ValueError {
158 ValueError::Unsupported {
159 format: self,
160 feature,
161 }
162 }
163
164 pub(crate) const fn require(self, feature: Feature) -> Result<(), ValueError> {
169 if self.supports(feature) {
170 Ok(())
171 } else {
172 Err(self.reject(feature))
173 }
174 }
175}
176
177impl fmt::Display for Format {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 f.write_str(match self {
180 Self::Json => "JSON",
181 Self::Json5 => "JSON5",
182 Self::Toml { inline_width: _ } => "TOML",
183 Self::Yaml => "YAML",
184 })
185 }
186}
187
188#[derive(Debug, Clone, Copy, Facet, PartialEq, Eq)]
191#[facet(proxy = usize)]
192pub struct InlineWidth(#[facet(opaque)] Columns);
193
194impl InlineWidth {
195 #[must_use]
197 pub const fn new(columns: usize) -> Option<Self> {
198 match Columns::new(columns) {
199 Some(columns) => Some(Self(columns)),
200 None => None,
201 }
202 }
203
204 #[must_use]
206 pub const fn get(self) -> usize {
207 self.0.get()
208 }
209}
210
211impl TryFrom<usize> for InlineWidth {
212 type Error = ColumnsError;
213
214 fn try_from(columns: usize) -> Result<Self, Self::Error> {
215 Columns::try_from(columns).map(Self)
216 }
217}
218
219impl From<&InlineWidth> for usize {
220 fn from(width: &InlineWidth) -> Self {
221 width.get()
222 }
223}
224
225#[cfg(test)]
226mod tests;