1use serde::{Deserialize, Serialize};
11
12macro_rules! string_enum {
13 ($(#[$meta:meta])* $vis:vis enum $name:ident { $($(#[$vmeta:meta])* $variant:ident => $wire:literal),+ $(,)? }) => {
14 $(#[$meta])*
15 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
17 $vis enum $name {
18 $(
19 #[doc = concat!("Wire value: `", $wire, "`.")]
20 $(#[$vmeta])*
21 #[serde(rename = $wire)]
22 $variant,
23 )+
24 }
25 };
26}
27
28macro_rules! string_enum_open {
29 ($(#[$meta:meta])* $vis:vis enum $name:ident { $($(#[$vmeta:meta])* $variant:ident => $wire:literal),+ $(,)? }) => {
30 $(#[$meta])*
31 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
33 $vis enum $name {
34 $(
35 #[doc = concat!("Wire value: `", $wire, "`.")]
36 $(#[$vmeta])*
37 #[serde(rename = $wire)]
38 $variant,
39 )+
40 #[serde(other)]
42 Unknown,
43 }
44 };
45}
46
47string_enum! {
48 pub enum OutputFormat {
50 Gltf => "GLTF",
51 Usdz => "USDZ",
52 Fbx => "FBX",
53 Obj => "OBJ",
54 Stl => "STL",
55 ThreeMf => "3MF",
56 }
57}
58
59string_enum! {
60 pub enum TextureFormat {
62 Bmp => "BMP", Dpx => "DPX", Hdr => "HDR", Jpeg => "JPEG",
63 OpenExr => "OPEN_EXR", Png => "PNG", Targa => "TARGA",
64 Tiff => "TIFF", Webp => "WEBP",
65 }
66}
67
68string_enum! {
69 pub enum RigType {
71 Biped => "biped", Quadruped => "quadruped", Hexapod => "hexapod",
72 Octopod => "octopod", Avian => "avian", Serpentine => "serpentine",
73 Aquatic => "aquatic", Others => "others",
74 }
75}
76
77string_enum_open! {
78 pub enum RigTypeResponse {
81 Biped => "biped", Quadruped => "quadruped", Hexapod => "hexapod",
82 Octopod => "octopod", Avian => "avian", Serpentine => "serpentine",
83 Aquatic => "aquatic", Others => "others",
84 }
85}
86
87string_enum! {
88 pub enum RigSpec {
90 Mixamo => "mixamo",
91 Tripo => "tripo",
92 }
93}
94
95string_enum! {
96 pub enum PostStyle {
98 Lego => "lego", Voxel => "voxel", Voronoi => "voronoi", Minecraft => "minecraft",
99 }
100}
101
102string_enum! {
103 pub enum Animation {
105 Idle => "preset:idle", Walk => "preset:walk", Run => "preset:run",
106 Dive => "preset:dive", Climb => "preset:climb", Jump => "preset:jump",
107 Slash => "preset:slash", Shoot => "preset:shoot", Hurt => "preset:hurt",
108 Fall => "preset:fall", Turn => "preset:turn",
109 QuadrupedWalk => "preset:quadruped:walk",
110 HexapodWalk => "preset:hexapod:walk",
111 OctopodWalk => "preset:octopod:walk",
112 SerpentineMarch => "preset:serpentine:march",
113 AquaticMarch => "preset:aquatic:march",
114 }
115}
116
117string_enum! {
118 pub enum TextureQuality {
124 Standard => "standard",
125 Detailed => "detailed",
126 Extreme => "extreme",
127 }
128}
129
130string_enum! {
131 pub enum GeometryQuality {
135 Standard => "standard",
136 Detailed => "detailed",
137 }
138}
139
140string_enum! {
141 pub enum TextureAlignment {
143 OriginalImage => "original_image",
144 Geometry => "geometry",
145 }
146}
147
148string_enum! {
149 pub enum Orientation {
151 Default => "default",
152 AlignImage => "align_image",
153 }
154}
155
156string_enum! {
157 pub enum FbxPreset {
159 Blender => "blender", Mixamo => "mixamo", ThreeDsMax => "3dsmax",
160 }
161}
162
163string_enum! {
164 pub enum ExportOrientation {
166 PlusX => "+x", PlusY => "+y", MinusX => "-x", MinusY => "-y",
167 }
168}
169
170string_enum! {
171 pub enum RigOutputFormat {
173 Glb => "glb",
174 Fbx => "fbx",
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn animation_serializes_with_preset_prefix() {
184 let json = serde_json::to_string(&Animation::QuadrupedWalk).unwrap();
185 assert_eq!(json, "\"preset:quadruped:walk\"");
186 }
187
188 #[test]
189 fn post_style_roundtrips() {
190 for s in [
191 PostStyle::Lego,
192 PostStyle::Voxel,
193 PostStyle::Voronoi,
194 PostStyle::Minecraft,
195 ] {
196 let j = serde_json::to_string(&s).unwrap();
197 let back: PostStyle = serde_json::from_str(&j).unwrap();
198 assert_eq!(s, back);
199 }
200 }
201
202 #[test]
203 fn strict_enum_rejects_unknown_value() {
204 let err = serde_json::from_str::<PostStyle>("\"brand_new_style\"").unwrap_err();
205 assert!(err.to_string().contains("unknown variant"), "{err}");
206 }
207
208 #[test]
209 fn strict_enum_rejects_wrong_case() {
210 let err = serde_json::from_str::<TextureQuality>("\"Detailed\"").unwrap_err();
214 assert!(err.to_string().contains("unknown variant"), "{err}");
215 }
216
217 #[test]
218 fn texture_quality_has_extreme_tier() {
219 let j = serde_json::to_string(&TextureQuality::Extreme).unwrap();
220 assert_eq!(j, "\"extreme\"");
221 let back: TextureQuality = serde_json::from_str("\"extreme\"").unwrap();
222 assert_eq!(back, TextureQuality::Extreme);
223 assert!(serde_json::from_str::<GeometryQuality>("\"extreme\"").is_err());
225 }
226
227 #[test]
228 fn open_enum_accepts_unknown_value() {
229 let got: RigTypeResponse = serde_json::from_str("\"brand_new_rig\"").unwrap();
230 assert_eq!(got, RigTypeResponse::Unknown);
231 }
232
233 #[test]
234 fn output_format_uppercase_wire() {
235 assert_eq!(
236 serde_json::to_string(&OutputFormat::ThreeMf).unwrap(),
237 "\"3MF\""
238 );
239 let gltf: OutputFormat = serde_json::from_str("\"GLTF\"").unwrap();
240 assert_eq!(gltf, OutputFormat::Gltf);
241 }
242
243 #[test]
244 fn export_orientation_sign_prefix() {
245 assert_eq!(
246 serde_json::to_string(&ExportOrientation::MinusY).unwrap(),
247 "\"-y\""
248 );
249 }
250}