atspi_common/
state.rs

1use enumflags2::{bitflags, BitFlag, BitFlags, FromBitsError};
2use serde::{
3	de::{self, Deserializer, Visitor},
4	ser::{SerializeSeq, Serializer},
5	Deserialize, Serialize,
6};
7use std::fmt;
8use zvariant::{Signature, Type};
9
10/// Used by various interfaces indicating every possible state
11/// of an accessibility object.
12#[bitflags]
13#[non_exhaustive]
14#[repr(u64)]
15#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
16#[serde(rename_all = "kebab-case")]
17pub enum State {
18	/// Indicates an invalid state - probably an error condition.
19	#[default]
20	Invalid,
21	/// Indicates a window is currently the active window, or
22	/// an object is the active subelement within a container or table.
23	///
24	/// `Active` should not be used for objects which have
25	/// [`State::Focusable`] or [`State::Selectable`]: Those objects should use
26	/// [`State::Focused`] and [`State::Selected`] respectively.
27	///
28	/// `Active` is a means to indicate that an object which is not
29	/// focusable and not selectable is the currently-active item within its
30	/// parent container.
31	Active,
32	/// Indicates that the object is armed.
33	Armed,
34	/// Indicates the current object is busy, i.e. onscreen
35	/// representation is in the process of changing, or       the object is
36	/// temporarily unavailable for interaction due to activity already in progress.
37	Busy,
38	/// Indicates this object is currently checked.
39	Checked,
40	/// Indicates this object is collapsed.
41	Collapsed,
42	/// Indicates that this object no longer has a valid
43	/// backing widget        (for instance, if its peer object has been destroyed).
44	Defunct,
45	/// Indicates the user can change the contents of this object.
46	Editable,
47	/// Indicates that this object is enabled, i.e. that it
48	/// currently reflects some application state. Objects that are "greyed out"
49	/// may lack this state, and may lack the [`State::Sensitive`] if direct
50	/// user interaction cannot cause them to acquire `Enabled`.
51	///
52	/// See [`State::Sensitive`].
53	Enabled,
54	/// Indicates this object allows progressive
55	/// disclosure of its children.
56	Expandable,
57	/// Indicates this object is expanded.
58	Expanded,
59	/// Indicates this object can accept keyboard focus,
60	/// which means all events resulting from typing on the keyboard will
61	/// normally be passed to it when it has focus.
62	Focusable,
63	/// Indicates this object currently has the keyboard focus.
64	Focused,
65	/// Indicates that the object has an associated tooltip.
66	HasTooltip,
67	/// Indicates the orientation of this object is horizontal.
68	Horizontal,
69	/// Indicates this object is minimized and is
70	/// represented only by an icon.
71	Iconified,
72	/// Indicates something must be done with this object
73	/// before the user can interact with an object in a different window.
74	Modal,
75	/// Indicates this (text) object can contain multiple
76	/// lines of text.
77	MultiLine,
78	/// Indicates this object allows more than one of
79	/// its children to be selected at the same time, or in the case of text
80	/// objects, that the object supports non-contiguous text selections.
81	Multiselectable,
82	/// Indicates this object paints every pixel within its
83	/// rectangular region. It also indicates an alpha value of unity, if it
84	/// supports alpha blending.
85	Opaque,
86	/// Indicates this object is currently pressed.
87	Pressed,
88	/// Indicates the size of this object's size is not fixed.
89	Resizable,
90	/// Indicates this object is the child of an object
91	/// that allows its children to be selected and that this child is one of
92	/// those children       that can be selected.
93	Selectable,
94	/// Indicates this object is the child of an object that
95	/// allows its children to be selected and that this child is one of those
96	/// children that has been selected.
97	Selected,
98	/// Indicates this object is sensitive, e.g. to user
99	/// interaction. `Sensitive` usually accompanies.
100	/// [`State::Enabled`] for user-actionable controls, but may be found in the
101	/// absence of [`State::Enabled`] if the current visible state of the control
102	/// is "disconnected" from the application state.  In such cases, direct user
103	/// interaction can often result in the object gaining `Sensitive`,
104	/// for instance if a user makes an explicit selection using an object whose
105	/// current state is ambiguous or undefined.
106	///
107	/// See [`State::Enabled`], [`State::Indeterminate`].
108	Sensitive,
109	/// Indicates this object, the object's parent, the
110	/// object's parent's parent, and so on, are all 'shown' to the end-user,
111	/// i.e. subject to "exposure" if blocking or obscuring objects do not
112	/// interpose between this object and the top of the window stack.
113	Showing,
114	/// Indicates this (text) object can contain only a
115	/// single line of text.
116	SingleLine,
117	/// Indicates that the information returned for this object
118	/// may no longer be synchronized with the application state.  This can occur
119	/// if the object has [`State::Transient`], and can also occur towards the
120	/// end of the object peer's lifecycle.
121	Stale,
122	/// Indicates this object is transient.
123	Transient,
124	/// Indicates the orientation of this object is vertical;
125	/// for example this state may appear on such objects as scrollbars, text
126	/// objects (with vertical text flow), separators, etc.
127	Vertical,
128	/// Indicates this object is visible, e.g. has been
129	/// explicitly marked for exposure to the user. `Visible` is no
130	/// guarantee that the object is actually unobscured on the screen, only that
131	/// it is 'potentially' visible, barring obstruction, being scrolled or clipped
132	/// out of the field of view, or having an ancestor container that has not yet
133	/// made visible. A widget is potentially onscreen if it has both
134	/// `Visible` and [`State::Showing`]. The absence of
135	/// `Visible` and [`State::Showing`] is
136	/// semantically equivalent to saying that an object is 'hidden'.
137	Visible,
138	/// Indicates that "active-descendant-changed"
139	/// event is sent when children become 'active' (i.e. are selected or
140	/// navigated to onscreen).  Used to prevent need to enumerate all children
141	/// in very large containers, like tables. The presence of
142	/// `ManagesDescendants` is an indication to the client that the
143	/// children should not, and need not, be enumerated by the client.
144	/// Objects implementing this state are expected to provide relevant state      
145	/// notifications to listening clients, for instance notifications of
146	/// visibility changes and activation of their contained child objects, without
147	/// the client having previously requested references to those children.
148	ManagesDescendants,
149	/// Indicates that a check box or other boolean
150	/// indicator is in a state other than checked or not checked.
151	///
152	/// This usually means that the boolean value reflected or controlled by the
153	/// object does not apply consistently to the entire current context.      
154	/// For example, a checkbox for the "Bold" attribute of text may have
155	/// `Indeterminate` if the currently selected text contains a mixture
156	/// of weight attributes. In many cases interacting with a
157	/// `Indeterminate` object will cause the context's corresponding
158	/// boolean attribute to be homogenized, whereupon the object will lose
159	/// `Indeterminate` and a corresponding state-changed event will be
160	/// fired.
161	Indeterminate,
162	/// Indicates that user interaction with this object is
163	/// 'required' from the user, for instance before completing the
164	/// processing of a form.
165	Required,
166	/// Indicates that an object's onscreen content
167	/// is truncated, e.g. a text value in a spreadsheet cell.
168	Truncated,
169	/// Indicates this object's visual representation is
170	/// dynamic, not static. This state may be applied to an object during an
171	/// animated 'effect' and be removed from the object once its visual
172	/// representation becomes static. Some applications, notably content viewers,
173	/// may not be able to detect all kinds of animated content.  Therefore the
174	/// absence of this state should not be taken as
175	/// definitive evidence that the object's visual representation is      
176	/// static; this state is advisory.
177	Animated,
178	/// This object has indicated an error condition
179	/// due to failure of input validation.  For instance, a form control may
180	/// acquire this state in response to invalid or malformed user input.
181	InvalidEntry,
182	/// This state indicates that the object
183	/// in question implements some form of typeahead or       
184	/// pre-selection behavior whereby entering the first character of one or more
185	/// sub-elements causes those elements to scroll into view or become
186	/// selected. Subsequent character input may narrow the selection further as
187	/// long as one or more sub-elements match the string. This state is normally
188	/// only useful and encountered on objects that implement [`crate::interface::Interface::Selection`].
189	/// In some cases the typeahead behavior may result in full or partial
190	/// completion of the data in the input field, in which case
191	/// these input events may trigger text-changed events from the source.
192	SupportsAutocompletion,
193	/// Indicates that the object in
194	/// question supports text selection. It should only be exposed on objects
195	/// which implement the [`crate::interface::Interface::Text`] interface, in order to distinguish this state
196	/// from [`State::Selectable`], which infers that the object in question is a
197	/// selectable child of an object which implements [`crate::interface::Interface::Selection`]. While
198	/// similar, text selection and subelement selection are distinct operations.
199	SelectableText,
200	/// Indicates that the object in question is
201	/// the 'default' interaction object in a dialog, i.e. the one that gets
202	/// activated if the user presses "Enter" when the dialog is initially
203	/// posted.
204	IsDefault,
205	/// Indicates that the object (typically a
206	/// hyperlink) has already been activated or invoked, with the result that
207	/// some backing data has been downloaded or rendered.
208	Visited,
209	/// Indicates this object has the potential to
210	/// be checked, such as a checkbox or toggle-able table cell.
211	Checkable,
212	/// Indicates that the object has a popup
213	/// context menu or sub-level menu which may or may not be
214	/// showing. This means that activation renders conditional content.
215	/// Note that ordinary tooltips are not considered popups in this
216	/// context.
217	HasPopup,
218	/// Indicates that an object which is [`State::Enabled`] and
219	/// [`State::Sensitive`] has a value which can be read, but not modified, by the
220	/// user.
221	ReadOnly,
222}
223
224impl From<State> for String {
225	fn from(state: State) -> String {
226		match state {
227			State::Invalid => "invalid",
228			State::Active => "active",
229			State::Armed => "armed",
230			State::Busy => "busy",
231			State::Checked => "checked",
232			State::Collapsed => "collapsed",
233			State::Defunct => "defunct",
234			State::Editable => "editable",
235			State::Enabled => "enabled",
236			State::Expandable => "expandable",
237			State::Expanded => "expanded",
238			State::Focusable => "focusable",
239			State::Focused => "focused",
240			State::HasTooltip => "has-tooltip",
241			State::Horizontal => "horizontal",
242			State::Iconified => "iconified",
243			State::Modal => "modal",
244			State::MultiLine => "multi-line",
245			State::Multiselectable => "multiselectable",
246			State::Opaque => "opaque",
247			State::Pressed => "pressed",
248			State::Resizable => "resizable",
249			State::Selectable => "selectable",
250			State::Selected => "selected",
251			State::Sensitive => "sensitive",
252			State::Showing => "showing",
253			State::SingleLine => "single-line",
254			State::Stale => "stale",
255			State::Transient => "transient",
256			State::Vertical => "vertical",
257			State::Visible => "visible",
258			State::ManagesDescendants => "manages-descendants",
259			State::Indeterminate => "indeterminate",
260			State::Required => "required",
261			State::Truncated => "truncated",
262			State::Animated => "animated",
263			State::InvalidEntry => "invalid-entry",
264			State::SupportsAutocompletion => "supports-autocompletion",
265			State::SelectableText => "selectable-text",
266			State::IsDefault => "is-default",
267			State::Visited => "visited",
268			State::Checkable => "checkable",
269			State::HasPopup => "has-popup",
270			State::ReadOnly => "read-only",
271		}
272		.to_string()
273	}
274}
275
276impl From<String> for State {
277	fn from(string: String) -> State {
278		(&*string).into()
279	}
280}
281
282impl From<&str> for State {
283	fn from(string: &str) -> State {
284		match string {
285			"active" => State::Active,
286			"armed" => State::Armed,
287			"busy" => State::Busy,
288			"checked" => State::Checked,
289			"collapsed" => State::Collapsed,
290			"defunct" => State::Defunct,
291			"editable" => State::Editable,
292			"enabled" => State::Enabled,
293			"expandable" => State::Expandable,
294			"expanded" => State::Expanded,
295			"focusable" => State::Focusable,
296			"focused" => State::Focused,
297			"has-tooltip" => State::HasTooltip,
298			"horizontal" => State::Horizontal,
299			"iconified" => State::Iconified,
300			"modal" => State::Modal,
301			"multi-line" => State::MultiLine,
302			"multiselectable" => State::Multiselectable,
303			"opaque" => State::Opaque,
304			"pressed" => State::Pressed,
305			"resizable" => State::Resizable,
306			"selectable" => State::Selectable,
307			"selected" => State::Selected,
308			"sensitive" => State::Sensitive,
309			"showing" => State::Showing,
310			"single-line" => State::SingleLine,
311			"stale" => State::Stale,
312			"transient" => State::Transient,
313			"vertical" => State::Vertical,
314			"visible" => State::Visible,
315			"manages-descendants" => State::ManagesDescendants,
316			"indeterminate" => State::Indeterminate,
317			"required" => State::Required,
318			"truncated" => State::Truncated,
319			"animated" => State::Animated,
320			"invalid-entry" => State::InvalidEntry,
321			"supports-autocompletion" => State::SupportsAutocompletion,
322			"selectable-text" => State::SelectableText,
323			"is-default" => State::IsDefault,
324			"visited" => State::Visited,
325			"checkable" => State::Checkable,
326			"has-popup" => State::HasPopup,
327			"read-only" => State::ReadOnly,
328			_ => State::Invalid,
329		}
330	}
331}
332
333#[allow(clippy::module_name_repetitions)]
334#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
335/// The bitflag representation of all states an object may have.
336pub struct StateSet(BitFlags<State>);
337
338impl StateSet {
339	/// Create a new `StateSet`.
340	///
341	///## Example
342	///```Rust
343	///     let states = State::Focusable | State::Sensitive | State::Active;
344	///     let set = StateSet::new(states);
345	///
346	///     assert!(set.contains(State::Active));
347	///     assert!(!set.contains(State::Busy));
348	/// ```
349	pub fn new<B: Into<BitFlags<State>>>(value: B) -> Self {
350		Self(value.into())
351	}
352
353	/// Returns the `StateSet` that corresponds to the provided `u64`s bit pattern.
354	///# Errors
355	/// When the argument encodes an undefined [`State`].
356	pub fn from_bits(bits: u64) -> Result<StateSet, FromBitsError<State>> {
357		Ok(StateSet(BitFlags::from_bits(bits)?))
358	}
359
360	#[must_use]
361	/// Create an empty `StateSet`
362	pub fn empty() -> StateSet {
363		StateSet(State::empty())
364	}
365
366	#[must_use]
367	/// Returns the state as represented by a u64.
368	pub fn bits(&self) -> u64 {
369		self.0.bits()
370	}
371
372	/// Whether the `StateSet` contains a [`State`].
373	pub fn contains<B: Into<BitFlags<State>>>(self, other: B) -> bool {
374		self.0.contains(other)
375	}
376
377	/// Removes a [`State`] (optionally) previously contained in the `StateSet`.
378	pub fn remove<B: Into<BitFlags<State>>>(&mut self, other: B) {
379		self.0.remove(other);
380	}
381
382	///  Inserts a [`State`] in the `StateSet`.
383	pub fn insert<B: Into<BitFlags<State>>>(&mut self, other: B) {
384		self.0.insert(other);
385	}
386
387	/// Returns an iterator that yields each set [`State`].
388	pub fn iter(self) -> impl Iterator<Item = State> {
389		self.0.iter()
390	}
391
392	#[must_use]
393	/// Checks if all states are unset.
394	pub fn is_empty(self) -> bool {
395		self.0.is_empty()
396	}
397
398	/// Returns true if at least one flag is shared.
399	pub fn intersects<B: Into<BitFlags<State>>>(self, other: B) -> bool {
400		self.0.intersects(other)
401	}
402
403	/// Toggles the matching bits.
404	pub fn toggle<B: Into<BitFlags<State>>>(&mut self, other: B) {
405		self.0.toggle(other);
406	}
407}
408
409impl<'de> Deserialize<'de> for StateSet {
410	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
411	where
412		D: Deserializer<'de>,
413	{
414		struct StateSetVisitor;
415
416		impl<'de> Visitor<'de> for StateSetVisitor {
417			type Value = StateSet;
418
419			fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
420				formatter
421					.write_str("a sequence comprised of two u32 that represents a valid StateSet")
422			}
423
424			fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
425			where
426				D: Deserializer<'de>,
427			{
428				match <Vec<u32> as Deserialize>::deserialize(deserializer) {
429					Ok(states) if states.len() == 2 => {
430						let mut bits = u64::from(states[0]);
431						bits |= (u64::from(states[1])) << 32;
432						StateSet::from_bits(bits).map_err(|_| de::Error::custom("invalid state"))
433					}
434					Ok(states) => Err(de::Error::invalid_length(states.len(), &"array of size 2")),
435					Err(e) => Err(e),
436				}
437			}
438		}
439
440		deserializer.deserialize_newtype_struct("StateSet", StateSetVisitor)
441	}
442}
443
444impl Serialize for StateSet {
445	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
446	where
447		S: Serializer,
448	{
449		let mut seq = serializer.serialize_seq(Some(2))?;
450		let bits = self.bits();
451
452		// This cast is safe and truncation is intentional.
453		//The shift is sound provided that `State` is `#[repr(u64)]`
454		#[allow(clippy::cast_possible_truncation)]
455		seq.serialize_element(&(bits as u32))?;
456		seq.serialize_element(&((bits >> 32) as u32))?;
457		seq.end()
458	}
459}
460
461impl Type for StateSet {
462	fn signature() -> Signature<'static> {
463		<Vec<u32> as Type>::signature()
464	}
465}
466
467impl From<State> for StateSet {
468	fn from(value: State) -> Self {
469		Self(value.into())
470	}
471}
472
473impl std::ops::BitXor for StateSet {
474	type Output = StateSet;
475
476	fn bitxor(self, other: Self) -> Self::Output {
477		StateSet(self.0 ^ other.0)
478	}
479}
480impl std::ops::BitXorAssign for StateSet {
481	fn bitxor_assign(&mut self, other: Self) {
482		self.0 = self.0 ^ other.0;
483	}
484}
485impl std::ops::BitOr for StateSet {
486	type Output = StateSet;
487
488	fn bitor(self, other: Self) -> Self::Output {
489		StateSet(self.0 | other.0)
490	}
491}
492impl std::ops::BitOrAssign for StateSet {
493	fn bitor_assign(&mut self, other: Self) {
494		self.0 = self.0 | other.0;
495	}
496}
497impl std::ops::BitAnd for StateSet {
498	type Output = StateSet;
499
500	fn bitand(self, other: Self) -> Self::Output {
501		StateSet(self.0 & other.0)
502	}
503}
504impl std::ops::BitAndAssign for StateSet {
505	fn bitand_assign(&mut self, other: Self) {
506		self.0 = self.0 & other.0;
507	}
508}
509
510#[cfg(test)]
511mod tests {
512	use super::{State, StateSet};
513	use zvariant::serialized::{Context, Data};
514	use zvariant::{to_bytes, LE};
515
516	#[test]
517	fn serialize_empty_state_set() {
518		let ctxt = Context::new_dbus(LE, 0);
519		let encoded = to_bytes(ctxt, &StateSet::empty()).unwrap();
520		assert_eq!(encoded.bytes(), &[8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
521	}
522
523	#[test]
524	fn deserialize_empty_state_set() {
525		let ctxt = Context::new_dbus(LE, 0);
526		let data = Data::new::<&[u8]>(&[8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ctxt);
527		let (decoded, _) = data.deserialize::<StateSet>().unwrap();
528		assert_eq!(decoded, StateSet::empty());
529	}
530
531	#[test]
532	fn serialize_state_set_invalid() {
533		let ctxt = Context::new_dbus(LE, 0);
534		let encoded = to_bytes(ctxt, &StateSet::new(State::Invalid)).unwrap();
535		assert_eq!(encoded.bytes(), &[8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]);
536	}
537
538	#[test]
539	fn deserialize_state_set_invalid() {
540		let ctxt = Context::new_dbus(LE, 0);
541		let data = Data::new::<&[u8]>(&[8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], ctxt);
542		let (decoded, _) = data.deserialize::<StateSet>().unwrap();
543		assert_eq!(decoded, StateSet::new(State::Invalid));
544	}
545
546	#[test]
547	fn serialize_state_set_manages_descendants() {
548		let ctxt = Context::new_dbus(LE, 0);
549		let encoded = to_bytes(ctxt, &StateSet::new(State::ManagesDescendants)).unwrap();
550		assert_eq!(encoded.bytes(), &[8, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0]);
551	}
552
553	#[test]
554	fn deserialize_state_set_manages_descendants() {
555		let ctxt = Context::new_dbus(LE, 0);
556		let data = Data::new::<&[u8]>(&[8, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0], ctxt);
557		let (decoded, _) = data.deserialize::<StateSet>().unwrap();
558		assert_eq!(decoded, StateSet::new(State::ManagesDescendants));
559	}
560
561	#[test]
562	fn serialize_state_set_indeterminate() {
563		let ctxt = Context::new_dbus(LE, 0);
564		let encoded = to_bytes(ctxt, &StateSet::new(State::Indeterminate)).unwrap();
565		assert_eq!(encoded.bytes(), &[8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]);
566	}
567
568	#[test]
569	fn deserialize_state_set_indeterminate() {
570		let ctxt = Context::new_dbus(LE, 0);
571		let data = Data::new::<&[u8]>(&[8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], ctxt);
572		let (decoded, _) = data.deserialize::<StateSet>().unwrap();
573		assert_eq!(decoded, StateSet::new(State::Indeterminate));
574	}
575
576	#[test]
577	fn serialize_state_set_focusable_focused() {
578		let ctxt = Context::new_dbus(LE, 0);
579		let encoded = to_bytes(ctxt, &StateSet::new(State::Focusable | State::Focused)).unwrap();
580		assert_eq!(encoded.bytes(), &[8, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0]);
581	}
582
583	#[test]
584	fn deserialize_state_set_focusable_focused() {
585		let ctxt = Context::new_dbus(LE, 0);
586		let data = Data::new::<&[u8]>(&[8, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0], ctxt);
587		let (decoded, _) = data.deserialize::<StateSet>().unwrap();
588		assert_eq!(decoded, StateSet::new(State::Focusable | State::Focused));
589	}
590
591	#[test]
592	fn cannot_deserialize_state_set_invalid_length() {
593		let ctxt = Context::new_dbus(LE, 0);
594		let data = Data::new::<&[u8]>(&[4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ctxt);
595		let decode_result = data.deserialize::<StateSet>();
596		assert!(decode_result.is_err());
597	}
598
599	#[test]
600	fn cannot_deserialize_state_set_invalid_flag() {
601		let ctxt = Context::new_dbus(LE, 0);
602		let data = Data::new::<&[u8]>(&[8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32], ctxt);
603		let decode_result = data.deserialize::<StateSet>();
604		assert!(decode_result.is_err());
605	}
606
607	#[test]
608	fn convert_state_direct_string() {
609		for state in StateSet::from_bits(0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111)
610			.unwrap()
611			.iter()
612		{
613			let state_str: String = state.into();
614			let state_two: State = state_str.clone().into();
615			assert_eq!(
616				state, state_two,
617				"The {state:?} was serialized as {state_str}, which deserializes to {state_two:?}"
618			);
619		}
620	}
621	#[test]
622	fn convert_state_direct_string_is_equal_to_serde_output() {
623		for state in StateSet::from_bits(0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111)
624			.unwrap()
625			.iter()
626		{
627			let serde_state_str: String = serde_plain::to_string(&state).unwrap();
628			let state_str: String = state.into();
629			assert_eq!(serde_state_str, state_str);
630			let state_two: State = serde_plain::from_str(&state_str).unwrap();
631			assert_eq!(state, state_two, "The {state:?} was serialized as {state_str}, which deserializes to {state_two:?} (serde)");
632		}
633	}
634}