morphix/adapter/mod.rs
1//! Adapters for serializing mutations to different formats.
2//!
3//! This module provides the [`Adapter`] trait and implementations for various serialization
4//! formats. Adapters bridge the gap between morphix's internal mutation representation and
5//! external formats like JSON or YAML.
6
7use std::mem::take;
8
9use crate::{Mutation, MutationError, MutationKind, Mutations, Path, PathSegment};
10
11#[cfg(feature = "json")]
12mod json;
13#[cfg(feature = "yaml")]
14mod yaml;
15
16#[cfg(feature = "json")]
17pub use json::Json;
18#[cfg(feature = "yaml")]
19pub use yaml::Yaml;
20
21/// Trait for adapting mutations to different serialization formats.
22///
23/// The [`Adapter`] trait provides an abstraction layer between the mutation detection system and
24/// the serialization format. This allows morphix to support multiple output formats while
25/// maintaining type safety.
26///
27/// ## Type Parameters
28///
29/// - [`Value`](Adapter::Value): Type used to represent [`Replace`](MutationKind::Replace) and
30/// [`Append`](MutationKind::Append) values.
31/// - [`Error`](Adapter::Error): Error type for serialization / deserialization operations.
32pub trait Adapter: Sized {
33 /// Type used to represent [`Replace`](MutationKind::Replace) and
34 /// [`Append`](MutationKind::Append) values.
35 type Value;
36
37 /// Error type for serialization / deserialization operations.
38 type Error;
39
40 /// Constructs the adapter from an optional mutation.
41 fn from_mutations(mutation: Mutations) -> Result<Self, Self::Error>;
42
43 /// Gets a mutable reference to a nested value by path segment.
44 ///
45 /// This method navigates into `value` using the provided `segment` and returns a mutable
46 /// reference to the nested value if it exists.
47 ///
48 /// ## Parameters
49 ///
50 /// - `value`: The value to navigate into
51 /// - `segment`: The path segment indicating which nested value to access
52 /// - `allow_create`: If `true` and the segment refers to a non-existent key in an object / map,
53 /// an empty value will be created at that location
54 ///
55 /// ## Returns
56 ///
57 /// - `Some(value)`: A mutable reference to the nested value
58 /// - `None`: If the operation is not supported on this value type, or if the segment refers to
59 /// a non-existent location and `allow_create` is `false`
60 fn get_mut<'a>(
61 value: &'a mut Self::Value,
62 segment: &PathSegment,
63 allow_create: bool,
64 ) -> Option<&'a mut Self::Value>;
65
66 /// Removes a value at the specified path segment.
67 ///
68 /// This method removes and returns the value at the location specified by `segment`
69 /// within `value`. It is used to apply [`Delete`](MutationKind::Delete) mutations.
70 ///
71 /// ## Parameters
72 ///
73 /// - `value`: The parent value containing the element to remove
74 /// - `segment`: The path segment indicating which nested value to remove
75 ///
76 /// ## Returns
77 ///
78 /// - `Some(removed_value)`: The removed value if the operation succeeded
79 /// - `None`: If the operation is not supported on this value type (e.g., not a map), or if the
80 /// segment refers to a non-existent location
81 #[cfg(feature = "delete")]
82 fn delete(value: &mut Self::Value, segment: &PathSegment) -> Option<Self::Value>;
83
84 /// Appends a value to the end of another value.
85 ///
86 /// This method performs an append operation similar to [`String::push_str`] or
87 /// [`Extend::extend`], merging `append_value` into the end of `value`.
88 ///
89 /// ## Parameters
90 ///
91 /// - `value`: The value to append to
92 /// - `append_value`: The value to be appended
93 ///
94 /// ## Returns
95 ///
96 /// - `Some(append_len)`: The length of the appended portion
97 /// - `None`: If the operation is not supported (e.g., incompatible types between `value` and
98 /// `append_value`, or `value` is not an appendable type)
99 ///
100 /// ## Note
101 ///
102 /// For strings, the returned length represents the char count, not the byte length.
103 #[cfg(feature = "append")]
104 fn append(value: &mut Self::Value, append_value: Self::Value) -> Option<usize>;
105
106 /// Returns the appendable length of a value.
107 ///
108 /// This method returns the current length of a value that can be used with
109 /// [`append`](Adapter::append) operations.
110 ///
111 /// ## Returns
112 ///
113 /// - `Some(len)`: The current length of the value
114 /// - `None`: If the value is not an appendable type
115 ///
116 /// ## Note
117 ///
118 /// For strings, the returned length represents the char count, not the byte length.
119 #[cfg(feature = "append")]
120 fn len(value: &Self::Value) -> Option<usize>;
121
122 /// Truncates a value by removing elements from the end.
123 ///
124 /// This method removes up to `truncate_len` elements from the end of `value`.
125 ///
126 /// ## Parameters
127 ///
128 /// - `value`: The value to truncate
129 /// - `truncate_len`: The number of elements to remove from the end
130 ///
131 /// ## Returns
132 ///
133 /// - `Some(remaining)`: The remaining truncation length that could not be applied. Returns `0`
134 /// if the full truncation was successful. If `truncate_len` exceeds the actual length,
135 /// returns `truncate_len - actual_len` and clears the value.
136 /// - `None`: If the operation is not supported on this value type
137 ///
138 /// ## Note
139 ///
140 /// For strings, the returned length represents the char count, not the byte length.
141 #[cfg(feature = "truncate")]
142 fn truncate(value: &mut Self::Value, truncate_len: usize) -> Option<usize>;
143
144 /// Applies a [Mutation] to an existing value.
145 fn mutate(
146 mut value: &mut Self::Value,
147 mut mutation: Mutation<Self::Value>,
148 path_stack: &mut Path<false>,
149 ) -> Result<(), MutationError> {
150 let is_replace = matches!(mutation.kind, MutationKind::Replace { .. });
151 #[cfg(feature = "delete")]
152 let is_delete = matches!(mutation.kind, MutationKind::Delete);
153
154 while let Some(segment) = mutation.path.pop() {
155 let is_last_segment = mutation.path.is_empty();
156 #[cfg(feature = "delete")]
157 if is_last_segment && is_delete {
158 match Self::delete(value, &segment) {
159 Some(_) => return Ok(()),
160 None => {
161 path_stack.push(segment);
162 return Err(MutationError::IndexError { path: take(path_stack) });
163 }
164 }
165 }
166 let inner_value = Self::get_mut(value, &segment, is_replace && is_last_segment);
167 path_stack.push(segment);
168 let Some(inner_value) = inner_value else {
169 return Err(MutationError::IndexError { path: take(path_stack) });
170 };
171 value = inner_value;
172 }
173 #[cfg(feature = "delete")]
174 if is_delete {
175 return Err(MutationError::IndexError { path: take(path_stack) });
176 }
177
178 match mutation.kind {
179 MutationKind::Replace(replace_value) => {
180 *value = replace_value;
181 }
182 #[cfg(feature = "append")]
183 MutationKind::Append(append_value) => {
184 if Self::append(value, append_value).is_none() {
185 return Err(MutationError::OperationError { path: take(path_stack) });
186 }
187 }
188 #[cfg(feature = "truncate")]
189 MutationKind::Truncate(truncate_len) => {
190 let Some(remaining) = Self::truncate(value, truncate_len) else {
191 return Err(MutationError::OperationError { path: take(path_stack) });
192 };
193 if remaining > 0 {
194 return Err(MutationError::TruncateError {
195 path: take(path_stack),
196 actual_len: truncate_len - remaining,
197 truncate_len,
198 });
199 }
200 }
201 #[cfg(feature = "delete")]
202 MutationKind::Delete => unreachable!(),
203 MutationKind::Batch(mutations) => {
204 let len = path_stack.len();
205 for mutation in mutations {
206 Self::mutate(value, mutation, path_stack)?;
207 path_stack.truncate(len);
208 }
209 }
210 }
211
212 Ok(())
213 }
214}