1use std::fmt;
10use std::ops::Deref;
11
12use schemars::JsonSchema;
13use schemars::r#gen::SchemaGenerator;
14use schemars::schema::{InstanceType, Schema, SchemaObject};
15use serde::{Deserialize, Deserializer, Serialize};
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
22#[serde(transparent)]
23pub struct PositiveI64(i64);
24
25impl PositiveI64 {
26 pub fn new(value: i64) -> Self {
28 assert!(value > 0, "PositiveI64: expected > 0, got {value}");
29 Self(value)
30 }
31
32 pub fn get(self) -> i64 {
33 self.0
34 }
35}
36
37impl<'de> Deserialize<'de> for PositiveI64 {
38 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
39 let v = i64::deserialize(deserializer)?;
40 if v <= 0 {
41 return Err(serde::de::Error::custom(format!(
42 "expected positive i64 (> 0), got {v}"
43 )));
44 }
45 Ok(Self(v))
46 }
47}
48
49impl JsonSchema for PositiveI64 {
50 fn schema_name() -> String {
51 "PositiveI64".to_owned()
52 }
53
54 fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
55 SchemaObject {
56 instance_type: Some(InstanceType::Integer.into()),
57 number: Some(Box::new(schemars::schema::NumberValidation {
58 minimum: Some(1.0),
59 ..Default::default()
60 })),
61 ..Default::default()
62 }
63 .into()
64 }
65}
66
67impl fmt::Display for PositiveI64 {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 self.0.fmt(f)
70 }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
78#[serde(transparent)]
79pub struct PositiveI32(i32);
80
81impl PositiveI32 {
82 pub fn new(value: i32) -> Self {
84 assert!(value > 0, "PositiveI32: expected > 0, got {value}");
85 Self(value)
86 }
87
88 pub fn get(self) -> i32 {
89 self.0
90 }
91}
92
93impl<'de> Deserialize<'de> for PositiveI32 {
94 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
95 let v = i32::deserialize(deserializer)?;
96 if v <= 0 {
97 return Err(serde::de::Error::custom(format!(
98 "expected positive i32 (> 0), got {v}"
99 )));
100 }
101 Ok(Self(v))
102 }
103}
104
105impl JsonSchema for PositiveI32 {
106 fn schema_name() -> String {
107 "PositiveI32".to_owned()
108 }
109
110 fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
111 SchemaObject {
112 instance_type: Some(InstanceType::Integer.into()),
113 number: Some(Box::new(schemars::schema::NumberValidation {
114 minimum: Some(1.0),
115 ..Default::default()
116 })),
117 ..Default::default()
118 }
119 .into()
120 }
121}
122
123impl fmt::Display for PositiveI32 {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 self.0.fmt(f)
126 }
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize)]
134#[serde(transparent)]
135pub struct PositiveF64(f64);
136
137impl PositiveF64 {
138 pub fn new(value: f64) -> Self {
140 assert!(value > 0.0, "PositiveF64: expected > 0.0, got {value}");
141 Self(value)
142 }
143
144 pub fn get(self) -> f64 {
145 self.0
146 }
147}
148
149impl<'de> Deserialize<'de> for PositiveF64 {
150 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
151 let v = f64::deserialize(deserializer)?;
152 if v <= 0.0 {
153 return Err(serde::de::Error::custom(format!(
154 "expected positive f64 (> 0.0), got {v}"
155 )));
156 }
157 Ok(Self(v))
158 }
159}
160
161impl JsonSchema for PositiveF64 {
162 fn schema_name() -> String {
163 "PositiveF64".to_owned()
164 }
165
166 fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
167 SchemaObject {
168 instance_type: Some(InstanceType::Number.into()),
169 number: Some(Box::new(schemars::schema::NumberValidation {
170 exclusive_minimum: Some(0.0),
171 ..Default::default()
172 })),
173 ..Default::default()
174 }
175 .into()
176 }
177}
178
179impl fmt::Display for PositiveF64 {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 self.0.fmt(f)
182 }
183}
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
190#[serde(transparent)]
191pub struct NonZeroU64(u64);
192
193impl NonZeroU64 {
194 pub fn new(value: u64) -> Self {
196 assert!(value != 0, "NonZeroU64: expected != 0, got 0");
197 Self(value)
198 }
199
200 pub fn get(self) -> u64 {
201 self.0
202 }
203}
204
205impl<'de> Deserialize<'de> for NonZeroU64 {
206 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
207 let v = u64::deserialize(deserializer)?;
208 if v == 0 {
209 return Err(serde::de::Error::custom("expected non-zero u64, got 0"));
210 }
211 Ok(Self(v))
212 }
213}
214
215impl JsonSchema for NonZeroU64 {
216 fn schema_name() -> String {
217 "NonZeroU64".to_owned()
218 }
219
220 fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
221 SchemaObject {
222 instance_type: Some(InstanceType::Integer.into()),
223 number: Some(Box::new(schemars::schema::NumberValidation {
224 minimum: Some(1.0),
225 ..Default::default()
226 })),
227 ..Default::default()
228 }
229 .into()
230 }
231}
232
233impl fmt::Display for NonZeroU64 {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 self.0.fmt(f)
236 }
237}
238
239#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize)]
244#[serde(transparent)]
245pub struct WeightMultiplier(f64);
246
247impl WeightMultiplier {
248 pub fn new(value: f64) -> Self {
250 assert!(
251 value >= 1.0,
252 "WeightMultiplier: expected >= 1.0, got {value}"
253 );
254 Self(value)
255 }
256
257 pub fn get(self) -> f64 {
258 self.0
259 }
260}
261
262impl<'de> Deserialize<'de> for WeightMultiplier {
263 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
264 let v = f64::deserialize(deserializer)?;
265 if v < 1.0 {
266 return Err(serde::de::Error::custom(format!(
267 "expected weight multiplier >= 1.0, got {v}"
268 )));
269 }
270 Ok(Self(v))
271 }
272}
273
274impl JsonSchema for WeightMultiplier {
275 fn schema_name() -> String {
276 "WeightMultiplier".to_owned()
277 }
278
279 fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
280 SchemaObject {
281 instance_type: Some(InstanceType::Number.into()),
282 number: Some(Box::new(schemars::schema::NumberValidation {
283 minimum: Some(1.0),
284 ..Default::default()
285 })),
286 ..Default::default()
287 }
288 .into()
289 }
290}
291
292impl fmt::Display for WeightMultiplier {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 self.0.fmt(f)
295 }
296}
297
298#[derive(Clone, Debug, Serialize)]
303#[serde(transparent)]
304pub struct NonEmptyVec<T>(Vec<T>);
305
306impl<T> NonEmptyVec<T> {
307 pub fn new(vec: Vec<T>) -> Self {
309 assert!(!vec.is_empty(), "NonEmptyVec: expected non-empty vec");
310 Self(vec)
311 }
312
313 pub fn first(&self) -> &T {
314 &self.0[0]
316 }
317
318 pub fn last(&self) -> &T {
319 self.0.last().unwrap()
321 }
322
323 pub fn into_inner(self) -> Vec<T> {
324 self.0
325 }
326}
327
328impl<T> Deref for NonEmptyVec<T> {
329 type Target = [T];
330
331 fn deref(&self) -> &[T] {
332 &self.0
333 }
334}
335
336impl<T> std::ops::DerefMut for NonEmptyVec<T> {
337 fn deref_mut(&mut self) -> &mut [T] {
338 &mut self.0
339 }
340}
341
342impl<'a, T> IntoIterator for &'a NonEmptyVec<T> {
343 type Item = &'a T;
344 type IntoIter = std::slice::Iter<'a, T>;
345
346 fn into_iter(self) -> Self::IntoIter {
347 self.0.iter()
348 }
349}
350
351impl<'a, T> IntoIterator for &'a mut NonEmptyVec<T> {
352 type Item = &'a mut T;
353 type IntoIter = std::slice::IterMut<'a, T>;
354
355 fn into_iter(self) -> Self::IntoIter {
356 self.0.iter_mut()
357 }
358}
359
360impl<T> IntoIterator for NonEmptyVec<T> {
361 type Item = T;
362 type IntoIter = std::vec::IntoIter<T>;
363
364 fn into_iter(self) -> Self::IntoIter {
365 self.0.into_iter()
366 }
367}
368
369impl<'de, T: Deserialize<'de>> Deserialize<'de> for NonEmptyVec<T> {
370 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
371 let v = Vec::<T>::deserialize(deserializer)?;
372 if v.is_empty() {
373 return Err(serde::de::Error::custom(
374 "expected non-empty array, got empty array",
375 ));
376 }
377 Ok(Self(v))
378 }
379}
380
381impl<T: JsonSchema> JsonSchema for NonEmptyVec<T> {
382 fn schema_name() -> String {
383 format!("NonEmptyVec_{}", T::schema_name())
384 }
385
386 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
387 let inner = generator.subschema_for::<Vec<T>>();
388 if let Schema::Object(mut obj) = inner {
390 let arr = obj.array.get_or_insert_with(Default::default);
391 arr.min_items = Some(1);
392 Schema::Object(obj)
393 } else {
394 inner
395 }
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[test]
404 fn positive_i64_rejects_zero() {
405 let result: Result<PositiveI64, _> = serde_json::from_str("0");
406 assert!(result.is_err());
407 }
408
409 #[test]
410 fn positive_i64_rejects_negative() {
411 let result: Result<PositiveI64, _> = serde_json::from_str("-5");
412 assert!(result.is_err());
413 }
414
415 #[test]
416 fn positive_i64_accepts_positive() {
417 let v: PositiveI64 = serde_json::from_str("42").unwrap();
418 assert_eq!(v.get(), 42);
419 }
420
421 #[test]
422 fn positive_f64_rejects_zero() {
423 let result: Result<PositiveF64, _> = serde_json::from_str("0.0");
424 assert!(result.is_err());
425 }
426
427 #[test]
428 fn positive_f64_accepts_positive() {
429 let v: PositiveF64 = serde_json::from_str("2.72").unwrap();
430 assert!((v.get() - 2.72).abs() < f64::EPSILON);
431 }
432
433 #[test]
434 fn non_zero_u64_rejects_zero() {
435 let result: Result<NonZeroU64, _> = serde_json::from_str("0");
436 assert!(result.is_err());
437 }
438
439 #[test]
440 fn non_zero_u64_accepts_positive() {
441 let v: NonZeroU64 = serde_json::from_str("100").unwrap();
442 assert_eq!(v.get(), 100);
443 }
444
445 #[test]
446 fn weight_multiplier_rejects_below_one() {
447 let result: Result<WeightMultiplier, _> = serde_json::from_str("0.5");
448 assert!(result.is_err());
449 }
450
451 #[test]
452 fn weight_multiplier_accepts_one() {
453 let v: WeightMultiplier = serde_json::from_str("1.0").unwrap();
454 assert!((v.get() - 1.0).abs() < f64::EPSILON);
455 }
456
457 #[test]
458 fn non_empty_vec_rejects_empty() {
459 let result: Result<NonEmptyVec<i32>, _> = serde_json::from_str("[]");
460 assert!(result.is_err());
461 }
462
463 #[test]
464 fn non_empty_vec_accepts_non_empty() {
465 let v: NonEmptyVec<i32> = serde_json::from_str("[1, 2, 3]").unwrap();
466 assert_eq!(v.len(), 3);
467 assert_eq!(v[0], 1);
468 }
469
470 #[test]
471 fn non_empty_vec_deref_to_slice() {
472 let v: NonEmptyVec<i32> = serde_json::from_str("[10, 20]").unwrap();
473 let sum: i32 = v.iter().sum();
474 assert_eq!(sum, 30);
475 }
476
477 #[test]
478 fn positive_i64_roundtrip() {
479 let v: PositiveI64 = serde_json::from_str("7").unwrap();
480 let json = serde_json::to_string(&v).unwrap();
481 assert_eq!(json, "7");
482 }
483
484 #[test]
485 fn non_empty_vec_roundtrip() {
486 let v: NonEmptyVec<i32> = serde_json::from_str("[1, 2]").unwrap();
487 let json = serde_json::to_string(&v).unwrap();
488 assert_eq!(json, "[1,2]");
489 }
490}