Skip to main content

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}