1use crate::schema::metadata::{MetadataEnvelope, TypeId};
16use golem_schema_derive::{FromSchema, IntoSchema};
17use serde::{Deserialize, Serialize};
18
19#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
42#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
43#[cfg_attr(feature = "full", desert(evolution()))]
44#[serde(tag = "kind", content = "value", rename_all = "kebab-case")]
45#[schema(named = "schema-type")]
46#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
47pub enum SchemaType {
48 Ref {
51 id: TypeId,
52 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
53 metadata: MetadataEnvelope,
54 },
55
56 Bool {
58 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
59 metadata: MetadataEnvelope,
60 },
61 S8 {
62 #[serde(
63 default,
64 skip_serializing_if = "Option::is_none",
65 deserialize_with = "deserialize_normalized_numeric_restrictions"
66 )]
67 restrictions: Option<NumericRestrictions>,
68 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
69 metadata: MetadataEnvelope,
70 },
71 S16 {
72 #[serde(
73 default,
74 skip_serializing_if = "Option::is_none",
75 deserialize_with = "deserialize_normalized_numeric_restrictions"
76 )]
77 restrictions: Option<NumericRestrictions>,
78 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
79 metadata: MetadataEnvelope,
80 },
81 S32 {
82 #[serde(
83 default,
84 skip_serializing_if = "Option::is_none",
85 deserialize_with = "deserialize_normalized_numeric_restrictions"
86 )]
87 restrictions: Option<NumericRestrictions>,
88 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
89 metadata: MetadataEnvelope,
90 },
91 S64 {
92 #[serde(
93 default,
94 skip_serializing_if = "Option::is_none",
95 deserialize_with = "deserialize_normalized_numeric_restrictions"
96 )]
97 restrictions: Option<NumericRestrictions>,
98 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
99 metadata: MetadataEnvelope,
100 },
101 U8 {
102 #[serde(
103 default,
104 skip_serializing_if = "Option::is_none",
105 deserialize_with = "deserialize_normalized_numeric_restrictions"
106 )]
107 restrictions: Option<NumericRestrictions>,
108 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
109 metadata: MetadataEnvelope,
110 },
111 U16 {
112 #[serde(
113 default,
114 skip_serializing_if = "Option::is_none",
115 deserialize_with = "deserialize_normalized_numeric_restrictions"
116 )]
117 restrictions: Option<NumericRestrictions>,
118 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
119 metadata: MetadataEnvelope,
120 },
121 U32 {
122 #[serde(
123 default,
124 skip_serializing_if = "Option::is_none",
125 deserialize_with = "deserialize_normalized_numeric_restrictions"
126 )]
127 restrictions: Option<NumericRestrictions>,
128 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
129 metadata: MetadataEnvelope,
130 },
131 U64 {
132 #[serde(
133 default,
134 skip_serializing_if = "Option::is_none",
135 deserialize_with = "deserialize_normalized_numeric_restrictions"
136 )]
137 restrictions: Option<NumericRestrictions>,
138 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
139 metadata: MetadataEnvelope,
140 },
141 F32 {
142 #[serde(
143 default,
144 skip_serializing_if = "Option::is_none",
145 deserialize_with = "deserialize_normalized_numeric_restrictions"
146 )]
147 restrictions: Option<NumericRestrictions>,
148 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
149 metadata: MetadataEnvelope,
150 },
151 F64 {
152 #[serde(
153 default,
154 skip_serializing_if = "Option::is_none",
155 deserialize_with = "deserialize_normalized_numeric_restrictions"
156 )]
157 restrictions: Option<NumericRestrictions>,
158 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
159 metadata: MetadataEnvelope,
160 },
161 Char {
162 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
163 metadata: MetadataEnvelope,
164 },
165 String {
166 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
167 metadata: MetadataEnvelope,
168 },
169
170 Record {
172 fields: Vec<NamedFieldType>,
173 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
174 metadata: MetadataEnvelope,
175 },
176 Variant {
177 cases: Vec<VariantCaseType>,
178 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
179 metadata: MetadataEnvelope,
180 },
181 Enum {
182 cases: Vec<String>,
183 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
184 metadata: MetadataEnvelope,
185 },
186 Flags {
187 flags: Vec<String>,
188 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
189 metadata: MetadataEnvelope,
190 },
191 Tuple {
192 elements: Vec<SchemaType>,
193 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
194 metadata: MetadataEnvelope,
195 },
196 List {
197 element: Box<SchemaType>,
198 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
199 metadata: MetadataEnvelope,
200 },
201 FixedList {
202 element: Box<SchemaType>,
203 length: u32,
204 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
205 metadata: MetadataEnvelope,
206 },
207 Map {
208 key: Box<SchemaType>,
209 value: Box<SchemaType>,
210 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
211 metadata: MetadataEnvelope,
212 },
213 Option {
214 inner: Box<SchemaType>,
215 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
216 metadata: MetadataEnvelope,
217 },
218 Result {
219 spec: ResultSpec,
220 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
221 metadata: MetadataEnvelope,
222 },
223
224 Text {
226 restrictions: TextRestrictions,
227 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
228 metadata: MetadataEnvelope,
229 },
230 Binary {
231 restrictions: BinaryRestrictions,
232 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
233 metadata: MetadataEnvelope,
234 },
235 Path {
236 spec: PathSpec,
237 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
238 metadata: MetadataEnvelope,
239 },
240 Url {
241 restrictions: UrlRestrictions,
242 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
243 metadata: MetadataEnvelope,
244 },
245 Uuid {
246 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
247 metadata: MetadataEnvelope,
248 },
249 Datetime {
250 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
251 metadata: MetadataEnvelope,
252 },
253 Duration {
254 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
255 metadata: MetadataEnvelope,
256 },
257 Quantity {
258 spec: QuantitySpec,
259 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
260 metadata: MetadataEnvelope,
261 },
262
263 Union {
265 spec: UnionSpec,
266 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
267 metadata: MetadataEnvelope,
268 },
269
270 Secret {
272 spec: SecretSpec,
273 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
274 metadata: MetadataEnvelope,
275 },
276 QuotaToken {
277 spec: QuotaTokenSpec,
278 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
279 metadata: MetadataEnvelope,
280 },
281 PermissionCard {
282 spec: PermissionCardSpec,
283 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
284 metadata: MetadataEnvelope,
285 },
286
287 Future {
289 inner: Option<Box<SchemaType>>,
290 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
291 metadata: MetadataEnvelope,
292 },
293 Stream {
294 inner: Option<Box<SchemaType>>,
295 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
296 metadata: MetadataEnvelope,
297 },
298}
299
300impl SchemaType {
301 pub fn metadata(&self) -> &MetadataEnvelope {
304 match self {
305 SchemaType::Ref { metadata, .. }
306 | SchemaType::Bool { metadata }
307 | SchemaType::S8 { metadata, .. }
308 | SchemaType::S16 { metadata, .. }
309 | SchemaType::S32 { metadata, .. }
310 | SchemaType::S64 { metadata, .. }
311 | SchemaType::U8 { metadata, .. }
312 | SchemaType::U16 { metadata, .. }
313 | SchemaType::U32 { metadata, .. }
314 | SchemaType::U64 { metadata, .. }
315 | SchemaType::F32 { metadata, .. }
316 | SchemaType::F64 { metadata, .. }
317 | SchemaType::Char { metadata }
318 | SchemaType::String { metadata }
319 | SchemaType::Record { metadata, .. }
320 | SchemaType::Variant { metadata, .. }
321 | SchemaType::Enum { metadata, .. }
322 | SchemaType::Flags { metadata, .. }
323 | SchemaType::Tuple { metadata, .. }
324 | SchemaType::List { metadata, .. }
325 | SchemaType::FixedList { metadata, .. }
326 | SchemaType::Map { metadata, .. }
327 | SchemaType::Option { metadata, .. }
328 | SchemaType::Result { metadata, .. }
329 | SchemaType::Text { metadata, .. }
330 | SchemaType::Binary { metadata, .. }
331 | SchemaType::Path { metadata, .. }
332 | SchemaType::Url { metadata, .. }
333 | SchemaType::Uuid { metadata }
334 | SchemaType::Datetime { metadata }
335 | SchemaType::Duration { metadata }
336 | SchemaType::Quantity { metadata, .. }
337 | SchemaType::Union { metadata, .. }
338 | SchemaType::Secret { metadata, .. }
339 | SchemaType::QuotaToken { metadata, .. }
340 | SchemaType::PermissionCard { metadata, .. }
341 | SchemaType::Future { metadata, .. }
342 | SchemaType::Stream { metadata, .. } => metadata,
343 }
344 }
345
346 pub fn metadata_mut(&mut self) -> &mut MetadataEnvelope {
348 match self {
349 SchemaType::Ref { metadata, .. }
350 | SchemaType::Bool { metadata }
351 | SchemaType::S8 { metadata, .. }
352 | SchemaType::S16 { metadata, .. }
353 | SchemaType::S32 { metadata, .. }
354 | SchemaType::S64 { metadata, .. }
355 | SchemaType::U8 { metadata, .. }
356 | SchemaType::U16 { metadata, .. }
357 | SchemaType::U32 { metadata, .. }
358 | SchemaType::U64 { metadata, .. }
359 | SchemaType::F32 { metadata, .. }
360 | SchemaType::F64 { metadata, .. }
361 | SchemaType::Char { metadata }
362 | SchemaType::String { metadata }
363 | SchemaType::Record { metadata, .. }
364 | SchemaType::Variant { metadata, .. }
365 | SchemaType::Enum { metadata, .. }
366 | SchemaType::Flags { metadata, .. }
367 | SchemaType::Tuple { metadata, .. }
368 | SchemaType::List { metadata, .. }
369 | SchemaType::FixedList { metadata, .. }
370 | SchemaType::Map { metadata, .. }
371 | SchemaType::Option { metadata, .. }
372 | SchemaType::Result { metadata, .. }
373 | SchemaType::Text { metadata, .. }
374 | SchemaType::Binary { metadata, .. }
375 | SchemaType::Path { metadata, .. }
376 | SchemaType::Url { metadata, .. }
377 | SchemaType::Uuid { metadata }
378 | SchemaType::Datetime { metadata }
379 | SchemaType::Duration { metadata }
380 | SchemaType::Quantity { metadata, .. }
381 | SchemaType::Union { metadata, .. }
382 | SchemaType::Secret { metadata, .. }
383 | SchemaType::QuotaToken { metadata, .. }
384 | SchemaType::PermissionCard { metadata, .. }
385 | SchemaType::Future { metadata, .. }
386 | SchemaType::Stream { metadata, .. } => metadata,
387 }
388 }
389
390 pub fn with_metadata(mut self, metadata: MetadataEnvelope) -> Self {
392 *self.metadata_mut() = metadata;
393 self
394 }
395
396 pub fn numeric_repr(&self) -> Option<NumericRepr> {
398 match self {
399 SchemaType::S8 { .. } => Some(NumericRepr::S8),
400 SchemaType::S16 { .. } => Some(NumericRepr::S16),
401 SchemaType::S32 { .. } => Some(NumericRepr::S32),
402 SchemaType::S64 { .. } => Some(NumericRepr::S64),
403 SchemaType::U8 { .. } => Some(NumericRepr::U8),
404 SchemaType::U16 { .. } => Some(NumericRepr::U16),
405 SchemaType::U32 { .. } => Some(NumericRepr::U32),
406 SchemaType::U64 { .. } => Some(NumericRepr::U64),
407 SchemaType::F32 { .. } => Some(NumericRepr::F32),
408 SchemaType::F64 { .. } => Some(NumericRepr::F64),
409 _ => None,
410 }
411 }
412
413 pub fn numeric_restrictions(&self) -> Option<&NumericRestrictions> {
416 match self {
417 SchemaType::S8 { restrictions, .. }
418 | SchemaType::S16 { restrictions, .. }
419 | SchemaType::S32 { restrictions, .. }
420 | SchemaType::S64 { restrictions, .. }
421 | SchemaType::U8 { restrictions, .. }
422 | SchemaType::U16 { restrictions, .. }
423 | SchemaType::U32 { restrictions, .. }
424 | SchemaType::U64 { restrictions, .. }
425 | SchemaType::F32 { restrictions, .. }
426 | SchemaType::F64 { restrictions, .. } => restrictions.as_ref(),
427 _ => None,
428 }
429 }
430
431 pub fn ref_to(id: TypeId) -> Self {
437 Self::Ref {
438 id,
439 metadata: MetadataEnvelope::default(),
440 }
441 }
442 pub fn bool() -> Self {
443 Self::Bool {
444 metadata: MetadataEnvelope::default(),
445 }
446 }
447 pub fn s8() -> Self {
448 Self::S8 {
449 restrictions: None,
450 metadata: MetadataEnvelope::default(),
451 }
452 }
453 pub fn s16() -> Self {
454 Self::S16 {
455 restrictions: None,
456 metadata: MetadataEnvelope::default(),
457 }
458 }
459 pub fn s32() -> Self {
460 Self::S32 {
461 restrictions: None,
462 metadata: MetadataEnvelope::default(),
463 }
464 }
465 pub fn s64() -> Self {
466 Self::S64 {
467 restrictions: None,
468 metadata: MetadataEnvelope::default(),
469 }
470 }
471 pub fn u8() -> Self {
472 Self::U8 {
473 restrictions: None,
474 metadata: MetadataEnvelope::default(),
475 }
476 }
477 pub fn u16() -> Self {
478 Self::U16 {
479 restrictions: None,
480 metadata: MetadataEnvelope::default(),
481 }
482 }
483 pub fn u32() -> Self {
484 Self::U32 {
485 restrictions: None,
486 metadata: MetadataEnvelope::default(),
487 }
488 }
489 pub fn u64() -> Self {
490 Self::U64 {
491 restrictions: None,
492 metadata: MetadataEnvelope::default(),
493 }
494 }
495 pub fn f32() -> Self {
496 Self::F32 {
497 restrictions: None,
498 metadata: MetadataEnvelope::default(),
499 }
500 }
501 pub fn f64() -> Self {
502 Self::F64 {
503 restrictions: None,
504 metadata: MetadataEnvelope::default(),
505 }
506 }
507 pub fn char() -> Self {
508 Self::Char {
509 metadata: MetadataEnvelope::default(),
510 }
511 }
512 pub fn string() -> Self {
513 Self::String {
514 metadata: MetadataEnvelope::default(),
515 }
516 }
517 pub fn uuid() -> Self {
518 Self::Uuid {
519 metadata: MetadataEnvelope::default(),
520 }
521 }
522 pub fn datetime() -> Self {
523 Self::Datetime {
524 metadata: MetadataEnvelope::default(),
525 }
526 }
527 pub fn duration() -> Self {
528 Self::Duration {
529 metadata: MetadataEnvelope::default(),
530 }
531 }
532 pub fn record(fields: Vec<NamedFieldType>) -> Self {
533 Self::Record {
534 fields,
535 metadata: MetadataEnvelope::default(),
536 }
537 }
538 pub fn record_from_fields<N>(fields: impl IntoIterator<Item = (N, SchemaType)>) -> Self
540 where
541 N: Into<String>,
542 {
543 Self::record(
544 fields
545 .into_iter()
546 .map(|(name, body)| NamedFieldType {
547 name: name.into(),
548 body,
549 metadata: MetadataEnvelope::default(),
550 })
551 .collect(),
552 )
553 }
554 pub fn variant(cases: Vec<VariantCaseType>) -> Self {
555 Self::Variant {
556 cases,
557 metadata: MetadataEnvelope::default(),
558 }
559 }
560 pub fn r#enum(cases: Vec<String>) -> Self {
561 Self::Enum {
562 cases,
563 metadata: MetadataEnvelope::default(),
564 }
565 }
566 pub fn flags(flags: Vec<String>) -> Self {
567 Self::Flags {
568 flags,
569 metadata: MetadataEnvelope::default(),
570 }
571 }
572 pub fn tuple(elements: Vec<SchemaType>) -> Self {
573 Self::Tuple {
574 elements,
575 metadata: MetadataEnvelope::default(),
576 }
577 }
578 pub fn list(element: SchemaType) -> Self {
579 Self::List {
580 element: Box::new(element),
581 metadata: MetadataEnvelope::default(),
582 }
583 }
584 pub fn fixed_list(element: SchemaType, length: u32) -> Self {
585 Self::FixedList {
586 element: Box::new(element),
587 length,
588 metadata: MetadataEnvelope::default(),
589 }
590 }
591 pub fn map(key: SchemaType, value: SchemaType) -> Self {
592 Self::Map {
593 key: Box::new(key),
594 value: Box::new(value),
595 metadata: MetadataEnvelope::default(),
596 }
597 }
598 pub fn option(inner: SchemaType) -> Self {
599 Self::Option {
600 inner: Box::new(inner),
601 metadata: MetadataEnvelope::default(),
602 }
603 }
604 pub fn result(spec: ResultSpec) -> Self {
605 Self::Result {
606 spec,
607 metadata: MetadataEnvelope::default(),
608 }
609 }
610 pub fn text(restrictions: TextRestrictions) -> Self {
611 Self::Text {
612 restrictions,
613 metadata: MetadataEnvelope::default(),
614 }
615 }
616 pub fn binary(restrictions: BinaryRestrictions) -> Self {
617 Self::Binary {
618 restrictions,
619 metadata: MetadataEnvelope::default(),
620 }
621 }
622 pub fn path(spec: PathSpec) -> Self {
623 Self::Path {
624 spec,
625 metadata: MetadataEnvelope::default(),
626 }
627 }
628 pub fn url(restrictions: UrlRestrictions) -> Self {
629 Self::Url {
630 restrictions,
631 metadata: MetadataEnvelope::default(),
632 }
633 }
634 pub fn quantity(spec: QuantitySpec) -> Self {
635 Self::Quantity {
636 spec,
637 metadata: MetadataEnvelope::default(),
638 }
639 }
640 pub fn union(spec: UnionSpec) -> Self {
641 Self::Union {
642 spec,
643 metadata: MetadataEnvelope::default(),
644 }
645 }
646 pub fn secret(spec: SecretSpec) -> Self {
647 Self::Secret {
648 spec,
649 metadata: MetadataEnvelope::default(),
650 }
651 }
652 pub fn quota_token(spec: QuotaTokenSpec) -> Self {
653 Self::QuotaToken {
654 spec,
655 metadata: MetadataEnvelope::default(),
656 }
657 }
658 pub fn permission_card(spec: PermissionCardSpec) -> Self {
659 Self::PermissionCard {
660 spec,
661 metadata: MetadataEnvelope::default(),
662 }
663 }
664 pub fn future(inner: Option<SchemaType>) -> Self {
665 Self::Future {
666 inner: inner.map(Box::new),
667 metadata: MetadataEnvelope::default(),
668 }
669 }
670 pub fn stream(inner: Option<SchemaType>) -> Self {
671 Self::Stream {
672 inner: inner.map(Box::new),
673 metadata: MetadataEnvelope::default(),
674 }
675 }
676}
677
678#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
679#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
680#[cfg_attr(feature = "full", desert(evolution()))]
681#[serde(rename_all = "camelCase")]
682#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
683pub struct NamedFieldType {
684 pub name: String,
685 pub body: SchemaType,
686 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
687 pub metadata: MetadataEnvelope,
688}
689
690#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
691#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
692#[cfg_attr(feature = "full", desert(evolution()))]
693#[serde(rename_all = "camelCase")]
694#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
695pub struct VariantCaseType {
696 pub name: String,
697 #[serde(default, skip_serializing_if = "Option::is_none")]
698 pub payload: Option<SchemaType>,
699 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
700 pub metadata: MetadataEnvelope,
701}
702
703#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
704#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
705#[cfg_attr(feature = "full", desert(evolution()))]
706#[serde(rename_all = "camelCase")]
707#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
708pub struct ResultSpec {
709 #[serde(default, skip_serializing_if = "Option::is_none")]
710 pub ok: Option<Box<SchemaType>>,
711 #[serde(default, skip_serializing_if = "Option::is_none")]
712 pub err: Option<Box<SchemaType>>,
713}
714
715#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
725#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
726#[cfg_attr(feature = "full", desert(evolution()))]
727#[serde(tag = "kind", content = "value", rename_all = "kebab-case")]
728#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
729pub enum NumericBound {
730 Signed(i64),
731 Unsigned(u64),
732 FloatBits(u64),
733}
734
735#[derive(Clone, Copy, Debug, PartialEq, Eq)]
737pub enum NumericBoundError {
738 NonFinite,
740}
741
742impl std::fmt::Display for NumericBoundError {
743 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
744 match self {
745 NumericBoundError::NonFinite => write!(f, "numeric float bound must be finite"),
746 }
747 }
748}
749
750impl std::error::Error for NumericBoundError {}
751
752impl NumericBound {
753 pub fn float(value: f64) -> Result<Self, NumericBoundError> {
756 if !value.is_finite() {
757 return Err(NumericBoundError::NonFinite);
758 }
759 let normalized = value + 0.0;
761 Ok(NumericBound::FloatBits(normalized.to_bits()))
762 }
763
764 pub fn as_f64(&self) -> Option<f64> {
766 match self {
767 NumericBound::FloatBits(bits) => Some(f64::from_bits(*bits)),
768 _ => None,
769 }
770 }
771}
772
773#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
781#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
782#[cfg_attr(feature = "full", desert(evolution()))]
783#[serde(rename_all = "camelCase")]
784#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
785pub struct NumericRestrictions {
786 #[serde(default, skip_serializing_if = "Option::is_none")]
787 pub min: Option<NumericBound>,
788 #[serde(default, skip_serializing_if = "Option::is_none")]
789 pub max: Option<NumericBound>,
790 #[serde(default, skip_serializing_if = "Option::is_none")]
794 pub unit: Option<String>,
795}
796
797impl NumericRestrictions {
798 pub fn is_empty(&self) -> bool {
800 self.min.is_none()
801 && self.max.is_none()
802 && self.unit.as_ref().map(|u| u.is_empty()).unwrap_or(true)
803 }
804
805 pub fn normalize(mut self) -> Option<Self> {
811 self.min = self.min.map(canonicalize_bound);
812 self.max = self.max.map(canonicalize_bound);
813 if self.unit.as_ref().map(|u| u.is_empty()).unwrap_or(false) {
814 self.unit = None;
815 }
816 if self.is_empty() { None } else { Some(self) }
817 }
818
819 pub fn validate_for_repr(&self, repr: NumericRepr) -> Result<(), NumericRestrictionError> {
828 if self.is_empty() {
829 return Err(NumericRestrictionError::EmptyStored);
830 }
831 if let Some(min) = self.min {
832 check_bound_repr(min, repr)?;
833 }
834 if let Some(max) = self.max {
835 check_bound_repr(max, repr)?;
836 }
837 if let (Some(min), Some(max)) = (self.min, self.max) {
838 match numeric_bound_cmp(min, max) {
839 Some(std::cmp::Ordering::Greater) => {
840 return Err(NumericRestrictionError::MinGreaterThanMax);
841 }
842 Some(_) => {}
843 None => return Err(NumericRestrictionError::FamilyMismatch),
844 }
845 }
846 Ok(())
847 }
848}
849
850#[derive(Clone, Copy, Debug, PartialEq, Eq)]
852pub enum NumericRepr {
853 S8,
854 S16,
855 S32,
856 S64,
857 U8,
858 U16,
859 U32,
860 U64,
861 F32,
862 F64,
863}
864
865impl NumericRepr {
866 pub fn is_integer(self) -> bool {
868 !matches!(self, NumericRepr::F32 | NumericRepr::F64)
869 }
870
871 fn family(self) -> NumericFamily {
872 match self {
873 NumericRepr::S8 | NumericRepr::S16 | NumericRepr::S32 | NumericRepr::S64 => {
874 NumericFamily::Signed
875 }
876 NumericRepr::U8 | NumericRepr::U16 | NumericRepr::U32 | NumericRepr::U64 => {
877 NumericFamily::Unsigned
878 }
879 NumericRepr::F32 | NumericRepr::F64 => NumericFamily::Float,
880 }
881 }
882
883 fn signed_range(self) -> Option<(i64, i64)> {
884 match self {
885 NumericRepr::S8 => Some((i8::MIN as i64, i8::MAX as i64)),
886 NumericRepr::S16 => Some((i16::MIN as i64, i16::MAX as i64)),
887 NumericRepr::S32 => Some((i32::MIN as i64, i32::MAX as i64)),
888 NumericRepr::S64 => Some((i64::MIN, i64::MAX)),
889 _ => None,
890 }
891 }
892
893 fn unsigned_max(self) -> Option<u64> {
894 match self {
895 NumericRepr::U8 => Some(u8::MAX as u64),
896 NumericRepr::U16 => Some(u16::MAX as u64),
897 NumericRepr::U32 => Some(u32::MAX as u64),
898 NumericRepr::U64 => Some(u64::MAX),
899 _ => None,
900 }
901 }
902}
903
904#[derive(Clone, Copy, Debug, PartialEq, Eq)]
905enum NumericFamily {
906 Signed,
907 Unsigned,
908 Float,
909}
910
911impl NumericBound {
912 fn family(self) -> NumericFamily {
913 match self {
914 NumericBound::Signed(_) => NumericFamily::Signed,
915 NumericBound::Unsigned(_) => NumericFamily::Unsigned,
916 NumericBound::FloatBits(_) => NumericFamily::Float,
917 }
918 }
919}
920
921fn canonicalize_bound(b: NumericBound) -> NumericBound {
927 match b {
928 NumericBound::FloatBits(bits) => {
929 if f64::from_bits(bits) == 0.0 {
930 NumericBound::FloatBits(0)
931 } else {
932 b
933 }
934 }
935 other => other,
936 }
937}
938
939fn deserialize_normalized_numeric_restrictions<'de, D>(
944 deserializer: D,
945) -> Result<Option<NumericRestrictions>, D::Error>
946where
947 D: serde::Deserializer<'de>,
948{
949 let raw = Option::<NumericRestrictions>::deserialize(deserializer)?;
950 Ok(raw.and_then(NumericRestrictions::normalize))
951}
952
953pub fn numeric_bound_cmp(a: NumericBound, b: NumericBound) -> Option<std::cmp::Ordering> {
956 match (a, b) {
957 (NumericBound::Signed(x), NumericBound::Signed(y)) => Some(x.cmp(&y)),
958 (NumericBound::Unsigned(x), NumericBound::Unsigned(y)) => Some(x.cmp(&y)),
959 (NumericBound::FloatBits(x), NumericBound::FloatBits(y)) => {
960 f64::from_bits(x).partial_cmp(&f64::from_bits(y))
962 }
963 _ => None,
964 }
965}
966
967fn check_bound_repr(bound: NumericBound, repr: NumericRepr) -> Result<(), NumericRestrictionError> {
968 if bound.family() != repr.family() {
969 return Err(NumericRestrictionError::FamilyMismatch);
970 }
971 match bound {
972 NumericBound::Signed(v) => {
973 let (lo, hi) = repr.signed_range().expect("signed family => signed range");
974 if v < lo || v > hi {
975 return Err(NumericRestrictionError::BoundOutOfRange);
976 }
977 }
978 NumericBound::Unsigned(v) => {
979 let hi = repr
980 .unsigned_max()
981 .expect("unsigned family => unsigned max");
982 if v > hi {
983 return Err(NumericRestrictionError::BoundOutOfRange);
984 }
985 }
986 NumericBound::FloatBits(bits) => {
987 let v = f64::from_bits(bits);
988 if !v.is_finite() {
989 return Err(NumericRestrictionError::NonFiniteFloat);
990 }
991 if repr == NumericRepr::F32 && (v as f32) as f64 != v {
992 return Err(NumericRestrictionError::FloatNotRoundTrippable);
993 }
994 }
995 }
996 Ok(())
997}
998
999#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1001pub enum NumericRestrictionError {
1002 EmptyStored,
1004 FamilyMismatch,
1006 BoundOutOfRange,
1008 NonFiniteFloat,
1010 FloatNotRoundTrippable,
1012 MinGreaterThanMax,
1014}
1015
1016impl std::fmt::Display for NumericRestrictionError {
1017 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1018 let msg = match self {
1019 NumericRestrictionError::EmptyStored => {
1020 "numeric restriction set is empty (must be None)"
1021 }
1022 NumericRestrictionError::FamilyMismatch => {
1023 "numeric bound family does not match the numeric type"
1024 }
1025 NumericRestrictionError::BoundOutOfRange => {
1026 "numeric bound does not fit the numeric type's range"
1027 }
1028 NumericRestrictionError::NonFiniteFloat => "numeric float bound must be finite",
1029 NumericRestrictionError::FloatNotRoundTrippable => {
1030 "f32 numeric bound does not round-trip through f32"
1031 }
1032 NumericRestrictionError::MinGreaterThanMax => {
1033 "numeric min bound is greater than max bound"
1034 }
1035 };
1036 write!(f, "{msg}")
1037 }
1038}
1039
1040#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1043#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1044#[cfg_attr(feature = "full", desert(evolution()))]
1045#[serde(rename_all = "camelCase")]
1046#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1047pub struct TextRestrictions {
1048 #[serde(default, skip_serializing_if = "Option::is_none")]
1050 pub languages: Option<Vec<String>>,
1051 #[serde(default, skip_serializing_if = "Option::is_none")]
1052 pub min_length: Option<u32>,
1053 #[serde(default, skip_serializing_if = "Option::is_none")]
1054 pub max_length: Option<u32>,
1055 #[serde(default, skip_serializing_if = "Option::is_none")]
1056 pub regex: Option<String>,
1057}
1058
1059#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1060#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1061#[cfg_attr(feature = "full", desert(evolution()))]
1062#[serde(rename_all = "camelCase")]
1063#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1064pub struct BinaryRestrictions {
1065 #[serde(default, skip_serializing_if = "Option::is_none")]
1067 pub mime_types: Option<Vec<String>>,
1068 #[serde(default, skip_serializing_if = "Option::is_none")]
1069 pub min_bytes: Option<u32>,
1070 #[serde(default, skip_serializing_if = "Option::is_none")]
1071 pub max_bytes: Option<u32>,
1072}
1073
1074#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1077#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1078#[cfg_attr(feature = "full", desert(evolution()))]
1079#[serde(rename_all = "kebab-case")]
1080#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1081pub enum PathDirection {
1082 Input,
1083 Output,
1084 InOut,
1085}
1086
1087#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1088#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1089#[cfg_attr(feature = "full", desert(evolution()))]
1090#[serde(rename_all = "kebab-case")]
1091#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1092pub enum PathKind {
1093 File,
1094 Directory,
1095 Any,
1096}
1097
1098#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1099#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1100#[cfg_attr(feature = "full", desert(evolution()))]
1101#[serde(rename_all = "camelCase")]
1102#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1103pub struct PathSpec {
1104 pub direction: PathDirection,
1105 pub kind: PathKind,
1106 #[serde(default, skip_serializing_if = "Option::is_none")]
1114 pub allowed_mime_types: Option<Vec<String>>,
1115 #[serde(default, skip_serializing_if = "Option::is_none")]
1116 pub allowed_extensions: Option<Vec<String>>,
1117}
1118
1119#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1122#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1123#[cfg_attr(feature = "full", desert(evolution()))]
1124#[serde(rename_all = "camelCase")]
1125#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1126pub struct UrlRestrictions {
1127 #[serde(default, skip_serializing_if = "Option::is_none")]
1128 pub allowed_schemes: Option<Vec<String>>,
1129 #[serde(default, skip_serializing_if = "Option::is_none")]
1130 pub allowed_hosts: Option<Vec<String>>,
1131}
1132
1133#[derive(
1139 Clone,
1140 Debug,
1141 PartialEq,
1142 Eq,
1143 Serialize,
1144 Deserialize,
1145 golem_schema_derive::IntoSchema,
1146 golem_schema_derive::FromSchema,
1147)]
1148#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1149#[cfg_attr(feature = "full", desert(evolution()))]
1150#[serde(rename_all = "camelCase")]
1151#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1152pub struct QuantityValue {
1153 pub mantissa: i64,
1154 pub scale: i32,
1155 pub unit: String,
1156}
1157
1158#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1159#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1160#[cfg_attr(feature = "full", desert(evolution()))]
1161#[serde(rename_all = "camelCase")]
1162#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1163pub struct QuantitySpec {
1164 pub base_unit: String,
1166 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1168 pub allowed_suffixes: Vec<String>,
1169 #[serde(default, skip_serializing_if = "Option::is_none")]
1171 pub min: Option<QuantityValue>,
1172 #[serde(default, skip_serializing_if = "Option::is_none")]
1173 pub max: Option<QuantityValue>,
1174}
1175
1176#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1179#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1180#[cfg_attr(feature = "full", desert(evolution()))]
1181#[serde(rename_all = "camelCase")]
1182#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1183pub struct UnionSpec {
1191 pub branches: Vec<UnionBranch>,
1192}
1193
1194#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1195#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1196#[cfg_attr(feature = "full", desert(evolution()))]
1197#[serde(rename_all = "camelCase")]
1198#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1199pub struct UnionBranch {
1200 pub tag: String,
1204 pub body: SchemaType,
1207 pub discriminator: DiscriminatorRule,
1209 #[serde(default, skip_serializing_if = "MetadataEnvelope::is_empty")]
1210 pub metadata: MetadataEnvelope,
1211}
1212
1213#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1215#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1216#[cfg_attr(feature = "full", desert(evolution()))]
1217#[serde(tag = "rule", content = "value", rename_all = "kebab-case")]
1218#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1219pub enum DiscriminatorRule {
1220 Prefix { prefix: String },
1222 Suffix { suffix: String },
1224 Contains { substring: String },
1226 Regex { regex: String },
1229 FieldEquals(FieldDiscriminator),
1232 FieldAbsent {
1234 #[serde(rename = "fieldName")]
1235 field_name: String,
1236 },
1237}
1238
1239#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1240#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1241#[cfg_attr(feature = "full", desert(evolution()))]
1242#[serde(rename_all = "camelCase")]
1243#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1244pub struct FieldDiscriminator {
1245 pub field_name: String,
1246 #[serde(default, skip_serializing_if = "Option::is_none")]
1247 pub literal: Option<String>,
1248}
1249
1250#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1253#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1254#[cfg_attr(feature = "full", desert(evolution()))]
1255#[serde(rename_all = "camelCase")]
1256#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1257pub struct SecretSpec {
1258 #[serde(default = "default_secret_inner")]
1260 pub inner: Box<SchemaType>,
1261 #[serde(default, skip_serializing_if = "Option::is_none")]
1263 pub category: Option<String>,
1264}
1265
1266fn default_secret_inner() -> Box<SchemaType> {
1267 Box::new(SchemaType::string())
1268}
1269
1270impl Default for SecretSpec {
1271 fn default() -> Self {
1272 Self {
1273 inner: default_secret_inner(),
1274 category: None,
1275 }
1276 }
1277}
1278
1279#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1280#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1281#[cfg_attr(feature = "full", desert(evolution()))]
1282#[serde(rename_all = "camelCase")]
1283#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1284pub struct QuotaTokenSpec {
1285 #[serde(default, skip_serializing_if = "Option::is_none")]
1288 pub resource_name: Option<String>,
1289}
1290
1291#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
1298#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
1299#[cfg_attr(feature = "full", desert(evolution()))]
1300#[serde(rename_all = "camelCase")]
1301#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
1302pub struct PermissionCardSpec {
1303 pub polymorphic: bool,
1305}
1306
1307#[cfg(test)]
1308mod tests {
1309 use super::*;
1310 use test_r::test;
1311
1312 #[test]
1313 fn record_from_fields_builds_fields_without_metadata() {
1314 assert_eq!(
1315 SchemaType::record_from_fields([("name", SchemaType::string())]),
1316 SchemaType::record(vec![NamedFieldType {
1317 name: "name".into(),
1318 body: SchemaType::string(),
1319 metadata: MetadataEnvelope::default(),
1320 }])
1321 );
1322 }
1323
1324 #[test]
1325 fn secret_spec_defaults_inner_to_string_when_absent() {
1326 let decoded: SecretSpec = serde_json::from_value(serde_json::json!({
1327 "category": "api-key"
1328 }))
1329 .expect("SecretSpec JSON without inner should deserialize");
1330
1331 assert_eq!(decoded.inner.as_ref(), &SchemaType::string());
1332 assert_eq!(decoded.category.as_deref(), Some("api-key"));
1333 }
1334
1335 #[test]
1336 fn normalize_drops_empty_to_none() {
1337 assert_eq!(NumericRestrictions::default().normalize(), None);
1338 assert_eq!(
1339 NumericRestrictions {
1340 min: None,
1341 max: None,
1342 unit: Some(String::new()),
1343 }
1344 .normalize(),
1345 None
1346 );
1347 }
1348
1349 #[test]
1350 fn normalize_canonicalizes_negative_zero_float_bound() {
1351 let neg_zero = NumericBound::FloatBits((-0.0f64).to_bits());
1352 let r = NumericRestrictions {
1353 min: Some(neg_zero),
1354 max: None,
1355 unit: None,
1356 }
1357 .normalize()
1358 .expect("non-empty restriction");
1359 assert_eq!(r.min, Some(NumericBound::FloatBits(0)));
1360 assert_eq!(r.min, Some(NumericBound::float(0.0).unwrap()));
1362 }
1363
1364 #[test]
1365 fn serde_empty_restrictions_object_deserializes_to_none() {
1366 let json = serde_json::json!({ "kind": "u32", "value": { "restrictions": {} } });
1367 let ty: SchemaType = serde_json::from_value(json).expect("deserialize");
1368 assert_eq!(ty.numeric_restrictions(), None);
1369 }
1370
1371 #[test]
1372 fn serde_empty_unit_deserializes_to_none() {
1373 let json =
1374 serde_json::json!({ "kind": "u32", "value": { "restrictions": { "unit": "" } } });
1375 let ty: SchemaType = serde_json::from_value(json).expect("deserialize");
1376 assert_eq!(ty.numeric_restrictions(), None);
1377 }
1378
1379 #[test]
1380 fn serde_empty_unit_with_bound_drops_only_the_unit() {
1381 let json = serde_json::json!({
1384 "kind": "u32",
1385 "value": {
1386 "restrictions": {
1387 "min": { "kind": "unsigned", "value": 1 },
1388 "unit": ""
1389 }
1390 }
1391 });
1392 let ty: SchemaType = serde_json::from_value(json).expect("deserialize");
1393 assert_eq!(
1394 ty.numeric_restrictions(),
1395 Some(&NumericRestrictions {
1396 min: Some(NumericBound::Unsigned(1)),
1397 max: None,
1398 unit: None,
1399 })
1400 );
1401 }
1402
1403 #[test]
1404 fn serde_canonicalizes_negative_zero_float_bound() {
1405 let neg_zero_bits = (-0.0f64).to_bits();
1406 let json = serde_json::json!({
1407 "kind": "f64",
1408 "value": { "restrictions": { "min": { "kind": "float-bits", "value": neg_zero_bits } } }
1409 });
1410 let ty: SchemaType = serde_json::from_value(json).expect("deserialize");
1411 assert_eq!(
1412 ty.numeric_restrictions().and_then(|r| r.min),
1413 Some(NumericBound::FloatBits(0))
1414 );
1415 }
1416
1417 #[test]
1418 fn serde_present_restrictions_round_trip() {
1419 let original = SchemaType::U32 {
1420 restrictions: NumericRestrictions {
1421 min: Some(NumericBound::Unsigned(1)),
1422 max: Some(NumericBound::Unsigned(100)),
1423 unit: Some("items".to_string()),
1424 }
1425 .normalize(),
1426 metadata: MetadataEnvelope::default(),
1427 };
1428 let json = serde_json::to_value(&original).expect("serialize");
1429 let back: SchemaType = serde_json::from_value(json).expect("deserialize");
1430 assert_eq!(original, back);
1431 }
1432}