1#![allow(
4 clippy::unreadable_literal,
5 reason = "samples are written as clients send them"
6)]
7
8use serde::de::DeserializeOwned;
9use serde_json::json;
10
11use super::{Fixture, Samples, Seed};
12
13fn parsed<T: DeserializeOwned>(json: serde_json::Value) -> T {
14 serde_json::from_value(json).expect("a fixture is valid JSON of its value")
15}
16
17#[cfg(feature = "percentage")]
18mod percentage {
19 use super::{Fixture, Samples, Seed, json, parsed};
20 use crate::Percentage;
21
22 impl Fixture for Percentage {
23 fn fixture(seed: &mut Seed) -> Self {
24 parsed(json!(seed.between(50, 2_500)))
25 }
26 }
27
28 samples!(Percentage, accepted: ["1900", "0", "\"19.5\""], refused: ["\"19%\"", "1.5"]);
29}
30
31#[cfg(feature = "person")]
32mod person {
33 use super::{Fixture, Samples, Seed, json, parsed};
34 use crate::PersonName;
35 use crate::fixture::{FIRST_NAMES, LAST_NAMES};
36
37 impl Fixture for PersonName {
38 fn fixture(seed: &mut Seed) -> Self {
39 let name = format!("{} {}", seed.pick(&FIRST_NAMES), seed.pick(&LAST_NAMES));
40 parsed(json!(name))
41 }
42 }
43
44 samples!(PersonName, accepted: ["\"Ada Lovelace\"", "\"Prince\""], refused: ["\"\"", "\" \""]);
45}
46
47#[cfg(feature = "phone")]
48mod phone {
49 use super::{Fixture, Samples, Seed, json, parsed};
50 use crate::PhoneNumber;
51
52 impl Fixture for PhoneNumber {
54 fn fixture(seed: &mut Seed) -> Self {
55 parsed(json!(format!("+49 30 2390{:04}", seed.between(0, 9_999))))
56 }
57 }
58
59 samples!(PhoneNumber, accepted: ["\"+49 30 23901234\"", "\"+1 202 555 0143\""], refused: ["\"555\"", "\"+\"", "\"phone\""]);
60}
61
62#[cfg(feature = "geo")]
63mod geo {
64 use super::{Fixture, Samples, Seed, json, parsed};
65 use crate::Coordinate;
66
67 const CITIES: [(f64, f64); 4] = [
68 (52.52, 13.405),
69 (41.0082, 28.9784),
70 (48.8566, 2.3522),
71 (51.5074, -0.1278),
72 ];
73
74 impl Fixture for Coordinate {
75 fn fixture(seed: &mut Seed) -> Self {
76 let (latitude, longitude) = *seed.pick(&CITIES);
77 parsed(json!({ "latitude": latitude, "longitude": longitude }))
78 }
79 }
80
81 samples!(
82 Coordinate,
83 accepted: [r#"{"latitude":52.52,"longitude":13.405}"#, r#"{"latitude":"52.52","longitude":"-13.4"}"#],
84 refused: [r#"{"latitude":91,"longitude":0}"#, r#"{"latitude":0,"longitude":181}"#, r#"{"latitude":"north","longitude":"0"}"#]
85 );
86}
87
88#[cfg(feature = "measure")]
89mod measure {
90 use super::{Fixture, Samples, Seed, json, parsed};
91 use crate::{Energy, Length, Measurement, Unit, Weight};
92
93 impl<U: Unit> Fixture for Measurement<U> {
94 fn fixture(seed: &mut Seed) -> Self {
95 let unit = *seed.pick(U::ALL);
96 let quantity = format!("{}.{}", seed.between(1, 250), seed.between(0, 9));
97 parsed(json!({ "quantity": quantity, "unit": unit.name() }))
98 }
99 }
100
101 samples!(Weight, accepted: [r#"{"quantity":"1.5","unit":"KILOGRAM"}"#], refused: [r#"{"quantity":"1.5","unit":"STONE"}"#, r#"{"quantity":"heavy","unit":"GRAM"}"#]);
102 samples!(Length, accepted: [r#"{"quantity":"12","unit":"METRE"}"#], refused: [r#"{"quantity":"1","unit":"PARSEC"}"#]);
103 samples!(Energy, accepted: [r#"{"quantity":"250","unit":"KILOCALORIE"}"#], refused: [r#"{"quantity":"1","unit":"ERG"}"#]);
104}
105
106#[cfg(feature = "time")]
107mod time {
108 use super::{Fixture, Samples, Seed, json, parsed};
109 use sofie_core::Variants;
110
111 use crate::{Date, LocalDateTime, TimeOfDay, TimeZone, Weekday};
112
113 const ZONES: [&str; 4] = [
114 "Europe/Berlin",
115 "Europe/Istanbul",
116 "America/New_York",
117 "Asia/Tokyo",
118 ];
119
120 fn date_text(seed: &mut Seed) -> String {
121 format!("2026-{:02}-{:02}", seed.between(1, 12), seed.between(1, 28))
122 }
123
124 impl Fixture for Date {
125 fn fixture(seed: &mut Seed) -> Self {
126 parsed(json!(date_text(seed)))
127 }
128 }
129
130 impl Fixture for TimeZone {
131 fn fixture(seed: &mut Seed) -> Self {
132 parsed(json!(seed.pick(&ZONES)))
133 }
134 }
135
136 impl Fixture for LocalDateTime {
137 fn fixture(seed: &mut Seed) -> Self {
138 let local = format!(
139 "{}T{:02}:{:02}:00",
140 date_text(seed),
141 seed.between(9, 17),
142 30 * seed.between(0, 1)
143 );
144 parsed(json!({ "local": local, "zone": seed.pick(&ZONES) }))
145 }
146 }
147
148 impl Fixture for TimeOfDay {
149 fn fixture(seed: &mut Seed) -> Self {
150 let time = format!("{:02}:{:02}", seed.between(6, 22), 15 * seed.between(0, 3));
151 parsed(json!(time))
152 }
153 }
154
155 impl Fixture for Weekday {
156 fn fixture(seed: &mut Seed) -> Self {
157 let names: Vec<&str> = Weekday::VARIANTS.iter().map(|(name, _)| *name).collect();
158 parsed(json!(seed.pick(&names)))
159 }
160 }
161
162 samples!(TimeOfDay, accepted: ["\"09:30\"", "\"00:00\""], refused: ["\"24:00\"", "\"9:30\""]);
163 samples!(Weekday, accepted: ["\"MONDAY\""], refused: ["\"monday\"", "\"FUNDAY\""]);
164 samples!(Date, accepted: ["\"2026-10-11\""], refused: ["\"2026-02-30\"", "\"11.10.2026\""]);
165 samples!(TimeZone, accepted: ["\"Europe/Istanbul\""], refused: ["\"Mars/Olympus\"", "\"\""]);
166 samples!(
167 LocalDateTime,
168 accepted: [r#"{"local":"2026-10-11T09:30:00","zone":"Europe/Istanbul"}"#, r#"{"local":"2026-10-11T09:30","zone":"Europe/Istanbul"}"#],
169 refused: [r#"{"local":"9:30","zone":"Europe/Istanbul"}"#, r#"{"local":"2026-10-11T09:30:00","zone":"Nowhere"}"#]
170 );
171}
172
173#[cfg(feature = "web")]
174mod web {
175 use super::{Fixture, Samples, Seed, json, parsed};
176 use crate::{Locale, Slug, Url};
177
178 const LOCALES: [&str; 4] = ["en", "de-DE", "tr-TR", "fr"];
179 const WORDS: [&str; 6] = ["spring", "sale", "linen", "shirt", "garden", "chair"];
180
181 impl Fixture for Locale {
182 fn fixture(seed: &mut Seed) -> Self {
183 parsed(json!(seed.pick(&LOCALES)))
184 }
185 }
186
187 impl Fixture for Slug {
188 fn fixture(seed: &mut Seed) -> Self {
189 parsed(json!(format!(
190 "{}-{}",
191 seed.pick(&WORDS),
192 seed.pick(&WORDS)
193 )))
194 }
195 }
196
197 impl Fixture for Url {
199 fn fixture(seed: &mut Seed) -> Self {
200 parsed(json!(format!("https://example.com/{}", seed.pick(&WORDS))))
201 }
202 }
203
204 samples!(Locale, accepted: ["\"de-DE\"", "\"en\""], refused: ["\"german\"", "\"\""]);
205 samples!(Slug, accepted: ["\"spring-sale\""], refused: ["\"Spring Sale\"", "\"\""]);
206 samples!(Url, accepted: ["\"https://example.com/a?b=c\""], refused: ["\"example\"", "\"javascript:alert(1)\""]);
207}