ddex_builder/presets/
generic.rs

1//! Generic industry-standard DDEX presets
2//!
3//! These presets provide baseline DDEX-compliant configurations that work
4//! across most platforms and distribution scenarios. They follow DDEX
5//! specification requirements without platform-specific customizations.
6
7use super::{
8    DdexVersion, MessageProfile, PartnerPreset, PresetConfig, PresetDefaults, PresetSource,
9    ValidationRule,
10};
11use indexmap::IndexMap;
12
13/// Generic Audio Album preset (ERN 4.3)
14///
15/// A baseline configuration for audio album releases that follows DDEX ERN 4.3
16/// specification requirements. This preset ensures compliance with core DDEX
17/// standards and can be used as a starting point for platform-specific customizations.
18pub fn audio_album() -> PartnerPreset {
19    let mut validation_rules = IndexMap::new();
20    validation_rules.insert("ISRC".to_string(), ValidationRule::Required);
21    validation_rules.insert("ReleaseDate".to_string(), ValidationRule::Required);
22    validation_rules.insert("Genre".to_string(), ValidationRule::Required);
23    validation_rules.insert("AlbumTitle".to_string(), ValidationRule::Required);
24    validation_rules.insert("ArtistName".to_string(), ValidationRule::Required);
25    validation_rules.insert("TrackTitle".to_string(), ValidationRule::Required);
26    validation_rules.insert(
27        "ISRC".to_string(),
28        ValidationRule::Pattern(r"^[A-Z]{2}[A-Z0-9]{3}\d{7}$".to_string()),
29    );
30    validation_rules.insert(
31        "Duration".to_string(),
32        ValidationRule::Pattern(r"^PT(\d+H)?(\d+M)?(\d+(\.\d+)?S)?$".to_string()),
33    );
34
35    let mut default_values = IndexMap::new();
36    default_values.insert("MessageControlType".to_string(), "LiveMessage".to_string());
37    default_values.insert("ReleaseType".to_string(), "Album".to_string());
38
39    let config = PresetConfig {
40        version: DdexVersion::Ern43,
41        profile: MessageProfile::AudioAlbum,
42        required_fields: vec![
43            "ISRC".to_string(),
44            "ReleaseDate".to_string(),
45            "Genre".to_string(),
46            "AlbumTitle".to_string(),
47            "ArtistName".to_string(),
48            "TrackTitle".to_string(),
49        ],
50        validation_rules: validation_rules.clone(),
51        default_values,
52        custom_mappings: IndexMap::new(),
53        territory_codes: vec!["Worldwide".to_string()],
54        distribution_channels: vec!["01".to_string()], // Download/Stream
55        release_types: vec![
56            "Album".to_string(),
57            "CompilationAlbum".to_string(),
58            "LiveAlbum".to_string(),
59        ],
60    };
61
62    PartnerPreset {
63        name: "audio_album".to_string(),
64        description: "Generic Audio Album ERN 4.3 - DDEX-compliant baseline configuration".to_string(),
65        source: PresetSource::Community,
66        provenance_url: Some("https://ddex.net/standards/".to_string()),
67        version: "1.0.0".to_string(),
68        locked: false,
69        disclaimer: "Generic industry-standard preset based on DDEX ERN 4.3 specification. Customize for specific platform requirements.".to_string(),
70        determinism: super::super::determinism::DeterminismConfig::default(),
71        defaults: PresetDefaults {
72            message_control_type: Some("LiveMessage".to_string()),
73            territory_code: vec!["Worldwide".to_string()],
74            distribution_channel: vec!["01".to_string()],
75        },
76        required_fields: config.required_fields.clone(),
77        format_overrides: IndexMap::new(),
78        config,
79        validation_rules,
80        custom_mappings: IndexMap::new(),
81    }
82}
83
84/// Generic Audio Single preset (ERN 4.3)
85///
86/// A baseline configuration for audio single releases following DDEX ERN 4.3
87/// specification. Optimized for single-track releases with simplified requirements.
88pub fn audio_single() -> PartnerPreset {
89    let mut validation_rules = IndexMap::new();
90    validation_rules.insert("ISRC".to_string(), ValidationRule::Required);
91    validation_rules.insert("ReleaseDate".to_string(), ValidationRule::Required);
92    validation_rules.insert("Genre".to_string(), ValidationRule::Required);
93    validation_rules.insert("TrackTitle".to_string(), ValidationRule::Required);
94    validation_rules.insert("ArtistName".to_string(), ValidationRule::Required);
95    validation_rules.insert(
96        "ISRC".to_string(),
97        ValidationRule::Pattern(r"^[A-Z]{2}[A-Z0-9]{3}\d{7}$".to_string()),
98    );
99    validation_rules.insert(
100        "Duration".to_string(),
101        ValidationRule::Pattern(r"^PT(\d+H)?(\d+M)?(\d+(\.\d+)?S)?$".to_string()),
102    );
103
104    let mut default_values = IndexMap::new();
105    default_values.insert("MessageControlType".to_string(), "LiveMessage".to_string());
106    default_values.insert("ReleaseType".to_string(), "Single".to_string());
107
108    let config = PresetConfig {
109        version: DdexVersion::Ern43,
110        profile: MessageProfile::AudioSingle,
111        required_fields: vec![
112            "ISRC".to_string(),
113            "ReleaseDate".to_string(),
114            "Genre".to_string(),
115            "TrackTitle".to_string(),
116            "ArtistName".to_string(),
117        ],
118        validation_rules: validation_rules.clone(),
119        default_values,
120        custom_mappings: IndexMap::new(),
121        territory_codes: vec!["Worldwide".to_string()],
122        distribution_channels: vec!["01".to_string()],
123        release_types: vec!["Single".to_string()],
124    };
125
126    PartnerPreset {
127        name: "audio_single".to_string(),
128        description: "Generic Audio Single ERN 4.3 - DDEX-compliant single track configuration".to_string(),
129        source: PresetSource::Community,
130        provenance_url: Some("https://ddex.net/standards/".to_string()),
131        version: "1.0.0".to_string(),
132        locked: false,
133        disclaimer: "Generic industry-standard preset based on DDEX ERN 4.3 specification. Customize for specific platform requirements.".to_string(),
134        determinism: super::super::determinism::DeterminismConfig::default(),
135        defaults: PresetDefaults {
136            message_control_type: Some("LiveMessage".to_string()),
137            territory_code: vec!["Worldwide".to_string()],
138            distribution_channel: vec!["01".to_string()],
139        },
140        required_fields: config.required_fields.clone(),
141        format_overrides: IndexMap::new(),
142        config,
143        validation_rules,
144        custom_mappings: IndexMap::new(),
145    }
146}
147
148/// Generic Video Single preset (ERN 4.3)
149///
150/// A baseline configuration for video releases with synchronized audio.
151/// Includes requirements for both audio and video resources.
152pub fn video_single() -> PartnerPreset {
153    let mut validation_rules = IndexMap::new();
154    validation_rules.insert("ISRC".to_string(), ValidationRule::Required);
155    validation_rules.insert("ReleaseDate".to_string(), ValidationRule::Required);
156    validation_rules.insert("Genre".to_string(), ValidationRule::Required);
157    validation_rules.insert("VideoTitle".to_string(), ValidationRule::Required);
158    validation_rules.insert("ArtistName".to_string(), ValidationRule::Required);
159    validation_rules.insert("VideoResource".to_string(), ValidationRule::Required);
160    validation_rules.insert("AudioResource".to_string(), ValidationRule::Required);
161    validation_rules.insert(
162        "ISRC".to_string(),
163        ValidationRule::Pattern(r"^[A-Z]{2}[A-Z0-9]{3}\d{7}$".to_string()),
164    );
165
166    let mut default_values = IndexMap::new();
167    default_values.insert("MessageControlType".to_string(), "LiveMessage".to_string());
168    default_values.insert("ReleaseType".to_string(), "VideoSingle".to_string());
169
170    let mut custom_mappings = IndexMap::new();
171    custom_mappings.insert(
172        "VideoResource".to_string(),
173        "VideoTechnicalResourceDetails".to_string(),
174    );
175    custom_mappings.insert(
176        "AudioResource".to_string(),
177        "SoundRecordingTechnicalResourceDetails".to_string(),
178    );
179
180    let config = PresetConfig {
181        version: DdexVersion::Ern43,
182        profile: MessageProfile::VideoSingle,
183        required_fields: vec![
184            "ISRC".to_string(),
185            "ReleaseDate".to_string(),
186            "Genre".to_string(),
187            "VideoTitle".to_string(),
188            "ArtistName".to_string(),
189            "VideoResource".to_string(),
190            "AudioResource".to_string(),
191        ],
192        validation_rules: validation_rules.clone(),
193        default_values,
194        custom_mappings: custom_mappings.clone(),
195        territory_codes: vec!["Worldwide".to_string()],
196        distribution_channels: vec!["01".to_string(), "02".to_string()], // Download + Streaming
197        release_types: vec!["VideoSingle".to_string(), "MusicVideo".to_string()],
198    };
199
200    PartnerPreset {
201        name: "video_single".to_string(),
202        description: "Generic Video Single ERN 4.3 - DDEX-compliant video release configuration".to_string(),
203        source: PresetSource::Community,
204        provenance_url: Some("https://ddex.net/standards/".to_string()),
205        version: "1.0.0".to_string(),
206        locked: false,
207        disclaimer: "Generic industry-standard preset based on DDEX ERN 4.3 specification. Customize for specific platform requirements.".to_string(),
208        determinism: super::super::determinism::DeterminismConfig::default(),
209        defaults: PresetDefaults {
210            message_control_type: Some("LiveMessage".to_string()),
211            territory_code: vec!["Worldwide".to_string()],
212            distribution_channel: vec!["01".to_string(), "02".to_string()],
213        },
214        required_fields: config.required_fields.clone(),
215        format_overrides: IndexMap::new(),
216        config,
217        validation_rules,
218        custom_mappings,
219    }
220}
221
222/// Generic Compilation preset (ERN 4.3)
223///
224/// A baseline configuration for compilation releases with multiple artists.
225/// Includes validation for various artist scenarios and copyright handling.
226pub fn compilation() -> PartnerPreset {
227    let mut preset = audio_album();
228
229    // Modify for compilation-specific settings
230    preset.name = "compilation".to_string();
231    preset.description =
232        "Generic Compilation ERN 4.3 - DDEX-compliant multi-artist compilation configuration"
233            .to_string();
234    preset.config.release_types = vec!["CompilationAlbum".to_string()];
235    preset
236        .config
237        .default_values
238        .insert("ReleaseType".to_string(), "CompilationAlbum".to_string());
239
240    // Add compilation-specific validation
241    preset
242        .validation_rules
243        .insert("CompilationIndicator".to_string(), ValidationRule::Required);
244    preset.validation_rules.insert(
245        "VariousArtists".to_string(),
246        ValidationRule::Custom("Multiple contributing artists".to_string()),
247    );
248    preset
249        .config
250        .required_fields
251        .push("CompilationIndicator".to_string());
252    preset
253        .required_fields
254        .push("CompilationIndicator".to_string());
255
256    preset
257}
258
259/// Get all generic presets
260pub fn all_generic_presets() -> IndexMap<String, PartnerPreset> {
261    let mut presets = IndexMap::new();
262    presets.insert("audio_album".to_string(), audio_album());
263    presets.insert("audio_single".to_string(), audio_single());
264    presets.insert("video_single".to_string(), video_single());
265    presets.insert("compilation".to_string(), compilation());
266    presets
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_audio_album_preset() {
275        let preset = audio_album();
276        assert_eq!(preset.name, "audio_album");
277        assert_eq!(preset.config.version, DdexVersion::Ern43);
278        assert_eq!(preset.config.profile, MessageProfile::AudioAlbum);
279        assert!(preset.required_fields.contains(&"ISRC".to_string()));
280        assert!(preset.required_fields.contains(&"AlbumTitle".to_string()));
281        assert_eq!(preset.source, PresetSource::Community);
282    }
283
284    #[test]
285    fn test_audio_single_preset() {
286        let preset = audio_single();
287        assert_eq!(preset.name, "audio_single");
288        assert_eq!(preset.config.profile, MessageProfile::AudioSingle);
289        assert!(preset.required_fields.contains(&"TrackTitle".to_string()));
290        assert!(!preset.required_fields.contains(&"AlbumTitle".to_string()));
291    }
292
293    #[test]
294    fn test_video_single_preset() {
295        let preset = video_single();
296        assert_eq!(preset.name, "video_single");
297        assert_eq!(preset.config.profile, MessageProfile::VideoSingle);
298        assert!(preset
299            .required_fields
300            .contains(&"VideoResource".to_string()));
301        assert!(preset
302            .required_fields
303            .contains(&"AudioResource".to_string()));
304    }
305
306    #[test]
307    fn test_compilation_preset() {
308        let preset = compilation();
309        assert_eq!(preset.name, "compilation");
310        assert!(preset
311            .config
312            .release_types
313            .contains(&"CompilationAlbum".to_string()));
314        assert!(preset
315            .required_fields
316            .contains(&"CompilationIndicator".to_string()));
317    }
318
319    #[test]
320    fn test_all_generic_presets() {
321        let presets = all_generic_presets();
322        assert_eq!(presets.len(), 4);
323        assert!(presets.contains_key("audio_album"));
324        assert!(presets.contains_key("audio_single"));
325        assert!(presets.contains_key("video_single"));
326        assert!(presets.contains_key("compilation"));
327    }
328
329    #[test]
330    fn test_generic_presets_source() {
331        let presets = all_generic_presets();
332        for (_, preset) in presets {
333            assert_eq!(preset.source, PresetSource::Community);
334            assert!(preset.disclaimer.contains("Generic industry-standard"));
335        }
336    }
337}