Skip to main content

tripo_api/
enums.rs

1//! Shared typed enums used across request and response structs.
2//!
3//! Two flavors:
4//! - `string_enum!` — strict. Unknown wire values fail to deserialize. Used
5//!   for request payloads so typos in user-supplied config (RON, CLI args)
6//!   surface as errors instead of silently becoming an `Unknown` variant.
7//! - `string_enum_open!` — forward-compatible. Adds a `#[serde(other)]
8//!   Unknown` catchall so new server-side values don't break response parsing.
9
10use 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            /// Unknown value received from a forward-compatible server.
41            #[serde(other)]
42            Unknown,
43        }
44    };
45}
46
47string_enum! {
48    /// Output mesh format accepted by `convert_model`.
49    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    /// Texture image format for `convert_model`.
61    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    /// Biological rig classification. Strict — used in `rig_model` requests.
70    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    /// Rig classification reported by `check_riggable`. Forward-compatible —
79    /// unrecognized server values deserialize as `Unknown`.
80    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    /// Target rigging convention.
89    pub enum RigSpec {
90        Mixamo => "mixamo",
91        Tripo  => "tripo",
92    }
93}
94
95string_enum! {
96    /// Post-processing stylization preset.
97    pub enum PostStyle {
98        Lego => "lego", Voxel => "voxel", Voronoi => "voronoi", Minecraft => "minecraft",
99    }
100}
101
102string_enum! {
103    /// Animation preset (`retarget_animation`). Values are prefixed `preset:`.
104    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    /// Texture / geometry quality level.
119    pub enum Quality {
120        Standard => "standard",
121        Detailed => "detailed",
122    }
123}
124
125string_enum! {
126    /// Texture alignment strategy (image-to-model / texture-model).
127    pub enum TextureAlignment {
128        OriginalImage => "original_image",
129        Geometry => "geometry",
130    }
131}
132
133string_enum! {
134    /// Output orientation hint (image-to-model).
135    pub enum Orientation {
136        Default => "default",
137        AlignImage => "align_image",
138    }
139}
140
141string_enum! {
142    /// FBX preset selection (`convert_model`).
143    pub enum FbxPreset {
144        Blender => "blender", Mixamo => "mixamo", ThreeDsMax => "3dsmax",
145    }
146}
147
148string_enum! {
149    /// Export orientation vector (`convert_model`).
150    pub enum ExportOrientation {
151        PlusX => "+x", PlusY => "+y", MinusX => "-x", MinusY => "-y",
152    }
153}
154
155string_enum! {
156    /// Output file format for `rig_model` / `retarget_animation`.
157    pub enum RigOutputFormat {
158        Glb => "glb",
159        Fbx => "fbx",
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn animation_serializes_with_preset_prefix() {
169        let json = serde_json::to_string(&Animation::QuadrupedWalk).unwrap();
170        assert_eq!(json, "\"preset:quadruped:walk\"");
171    }
172
173    #[test]
174    fn post_style_roundtrips() {
175        for s in [
176            PostStyle::Lego,
177            PostStyle::Voxel,
178            PostStyle::Voronoi,
179            PostStyle::Minecraft,
180        ] {
181            let j = serde_json::to_string(&s).unwrap();
182            let back: PostStyle = serde_json::from_str(&j).unwrap();
183            assert_eq!(s, back);
184        }
185    }
186
187    #[test]
188    fn strict_enum_rejects_unknown_value() {
189        let err = serde_json::from_str::<PostStyle>("\"brand_new_style\"").unwrap_err();
190        assert!(err.to_string().contains("unknown variant"), "{err}");
191    }
192
193    #[test]
194    fn strict_enum_rejects_wrong_case() {
195        // Guards against the footgun where e.g. `"Detailed"` silently became
196        // `Quality::Unknown` instead of erroring, since the wire value is
197        // lowercase `"detailed"`.
198        let err = serde_json::from_str::<Quality>("\"Detailed\"").unwrap_err();
199        assert!(err.to_string().contains("unknown variant"), "{err}");
200    }
201
202    #[test]
203    fn open_enum_accepts_unknown_value() {
204        let got: RigTypeResponse = serde_json::from_str("\"brand_new_rig\"").unwrap();
205        assert_eq!(got, RigTypeResponse::Unknown);
206    }
207
208    #[test]
209    fn output_format_uppercase_wire() {
210        assert_eq!(
211            serde_json::to_string(&OutputFormat::ThreeMf).unwrap(),
212            "\"3MF\""
213        );
214        let gltf: OutputFormat = serde_json::from_str("\"GLTF\"").unwrap();
215        assert_eq!(gltf, OutputFormat::Gltf);
216    }
217
218    #[test]
219    fn export_orientation_sign_prefix() {
220        assert_eq!(
221            serde_json::to_string(&ExportOrientation::MinusY).unwrap(),
222            "\"-y\""
223        );
224    }
225}