development
rs 90 lines 3.5 KB
Raw
1 //! String to enum mapping for values that arrive as text from a command
2 //! line or a tool call.
3 //!
4 //! Each mapper's error lists the accepted values. That matters more for
5 //! the MCP surface than the CLI one: clap rejects a bad value before the
6 //! program runs, but a model that guessed wrong only learns from the
7 //! error text, and a bare "invalid value" costs it another round trip.
8
9 use smbcloud_ascapi_aso::app_screenshot_set::ScreenshotDisplayType;
10 use smbcloud_ascapi_aso::app_store_version::Platform;
11 use smbcloud_ascapi_aso::bundle_id::BundleIdPlatform;
12
13 pub fn platform_from_str(value: &str) -> Result<Platform, String> {
14 match value.to_ascii_lowercase().as_str() {
15 "ios" => Ok(Platform::Ios),
16 "mac_os" | "macos" => Ok(Platform::MacOs),
17 "tv_os" | "tvos" => Ok(Platform::TvOs),
18 "vision_os" | "visionos" => Ok(Platform::VisionOs),
19 other => Err(format!(
20 "unknown platform {other:?}; expected one of ios, mac_os, tv_os, vision_os"
21 )),
22 }
23 }
24
25 pub fn bundle_id_platform_from_str(value: &str) -> Result<BundleIdPlatform, String> {
26 match value.to_ascii_lowercase().as_str() {
27 // visionOS shares the iOS identifier namespace, so there is no
28 // separate value to offer here.
29 "ios" => Ok(BundleIdPlatform::Ios),
30 "mac_os" | "macos" => Ok(BundleIdPlatform::MacOs),
31 "universal" => Ok(BundleIdPlatform::Universal),
32 // Accepted so a round trip through this mapper does not lose a
33 // value the API itself returns. Registering one is a different
34 // flow than an app bundle ID, and Apple rejects it here.
35 "services" => Ok(BundleIdPlatform::Services),
36 other => Err(format!(
37 "unknown bundle ID platform {other:?}; expected one of ios, mac_os, universal"
38 )),
39 }
40 }
41
42 /// App Store Connect's display types are already screaming snake case, so
43 /// this accepts them verbatim and only normalizes the casing.
44 pub fn display_type_from_str(value: &str) -> Result<ScreenshotDisplayType, String> {
45 let upper = value.to_ascii_uppercase();
46 serde_json::from_value::<ScreenshotDisplayType>(serde_json::Value::String(upper.clone()))
47 .map_err(|_| {
48 format!(
49 "unknown screenshot display type {value:?}; expected an App Store Connect \
50 display type such as APP_IPHONE_67, APP_IPAD_PRO_129, APP_APPLE_VISION_PRO, \
51 or APP_DESKTOP"
52 )
53 })
54 }
55
56 #[cfg(test)]
57 mod tests {
58 use super::*;
59
60 #[test]
61 fn platform_accepts_both_spellings() {
62 assert_eq!(platform_from_str("visionos").unwrap(), Platform::VisionOs);
63 assert_eq!(platform_from_str("VISION_OS").unwrap(), Platform::VisionOs);
64 }
65
66 #[test]
67 fn errors_name_the_accepted_values() {
68 // A model that guessed wrong has to be able to fix itself from the
69 // error alone, without another call.
70 let error = platform_from_str("watchos").unwrap_err();
71 assert!(
72 error.contains("ios") && error.contains("vision_os"),
73 "{error}"
74 );
75
76 let error = display_type_from_str("iphone").unwrap_err();
77 assert!(error.contains("APP_IPHONE_67"), "{error}");
78
79 let error = bundle_id_platform_from_str("visionos").unwrap_err();
80 assert!(error.contains("universal"), "{error}");
81 }
82
83 #[test]
84 fn display_type_round_trips_a_real_value() {
85 assert_eq!(
86 display_type_from_str("app_iphone_67").unwrap(),
87 ScreenshotDisplayType::Iphone67
88 );
89 }
90 }