golem_schema/schema/metadata.rs
1// Copyright 2024-2026 Golem Cloud
2//
3// Licensed under the Golem Source License v1.1 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://license.golem.cloud/LICENSE
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use golem_schema_derive::{FromSchema, IntoSchema};
16use serde::{Deserialize, Serialize};
17use std::fmt::{Display, Formatter};
18
19/// Stable, language-independent identifier for a named type definition. Must
20/// be unique within the enclosing [`super::SchemaGraph`]. Conventional format
21/// is a dot-separated namespace path (e.g., `"myapp.users.user"`). Each SDK
22/// provides a default derivation rule (typically based on the local language's
23/// type name); cross-language interop requires the same `TypeId` on every side,
24/// which users can pin via the SDK's `named` attribute.
25#[derive(
26 Clone,
27 Debug,
28 PartialEq,
29 Eq,
30 PartialOrd,
31 Ord,
32 Hash,
33 Serialize,
34 Deserialize,
35 IntoSchema,
36 FromSchema,
37)]
38#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
39#[cfg_attr(feature = "full", desert(transparent))]
40#[serde(transparent)]
41#[schema(transparent)]
42#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
43pub struct TypeId(pub String);
44
45impl TypeId {
46 pub fn new(id: impl Into<String>) -> Self {
47 Self(id.into())
48 }
49
50 pub fn as_str(&self) -> &str {
51 &self.0
52 }
53}
54
55impl Display for TypeId {
56 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
57 f.write_str(&self.0)
58 }
59}
60
61impl From<String> for TypeId {
62 fn from(value: String) -> Self {
63 Self(value)
64 }
65}
66
67impl From<&str> for TypeId {
68 fn from(value: &str) -> Self {
69 Self(value.to_string())
70 }
71}
72
73/// Typed metadata envelope. Holds non-validation, non-rendering-critical
74/// information (docs, aliases, examples, deprecation, role). Per-scalar
75/// validation constraints live on the relevant scalar's typed substructure,
76/// not here.
77#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
78#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
79#[cfg_attr(feature = "full", desert(evolution()))]
80#[serde(rename_all = "camelCase")]
81#[schema(named = "metadata-envelope")]
82#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
83pub struct MetadataEnvelope {
84 /// Free-form documentation string.
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub doc: Option<String>,
87 /// Alternative names this type is also known by.
88 #[serde(default, skip_serializing_if = "Vec::is_empty")]
89 pub aliases: Vec<String>,
90 /// Canonical-encoded example values, each as a JSON string. Empty = no
91 /// examples. Stored as strings so metadata is self-contained on the type
92 /// side and does not have to cross-reference an accompanying value tree.
93 #[serde(default, skip_serializing_if = "Vec::is_empty")]
94 pub examples: Vec<String>,
95 /// Deprecation message; `None` means not deprecated.
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub deprecated: Option<String>,
98 /// Optional role annotation tagging a type with a consumer-facing intent.
99 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub role: Option<Role>,
101}
102
103impl MetadataEnvelope {
104 pub fn is_empty(&self) -> bool {
105 self.doc.is_none()
106 && self.aliases.is_empty()
107 && self.examples.is_empty()
108 && self.deprecated.is_none()
109 && self.role.is_none()
110 }
111}
112
113/// Open registry of consumer-facing roles a type may carry. Unknown roles are
114/// preserved as [`Role::Other`] so the producer's intent is not lost when a
115/// receiver does not understand the role.
116#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, IntoSchema, FromSchema)]
117#[cfg_attr(feature = "full", derive(desert_rust::BinaryCodec))]
118#[cfg_attr(feature = "full", desert(evolution()))]
119#[serde(tag = "tag", content = "value", rename_all = "kebab-case")]
120#[schema(named = "role")]
121#[cfg_attr(feature = "full", derive(golem_schema_derive::PoemSchema))]
122pub enum Role {
123 /// Marks a `list<variant<…>>` whose elements are interchangeable
124 /// modalities (text / image / audio / …). See the multimodal adapters.
125 Multimodal,
126 /// Marks a `variant { inline: text, url: url }` as the ergonomic
127 /// "unstructured text" wrapper: a payload that is either inline text or a
128 /// URL referencing the text.
129 UnstructuredText,
130 /// Marks a `variant { inline: binary, url: url }` as the ergonomic
131 /// "unstructured binary" wrapper: a payload that is either inline bytes or
132 /// a URL referencing the bytes.
133 UnstructuredBinary,
134 /// Any other producer-defined role. Preserved verbatim so a receiver that
135 /// does not understand it does not lose the producer's intent.
136 Other(String),
137}