1use super::canonical::CanonicalSurfaceRef;
11use super::{CommandBody, ErrorCase, FlagShape, OptionShape, Tool, validation::validate_tool};
12use crate::schema::graph::{SchemaGraph, SchemaTypeDef};
13use crate::schema::metadata::TypeId;
14use crate::schema::schema_type::SchemaType;
15use crate::schema::schema_value::SchemaValue;
16use crate::schema::schema_value::{ResultValuePayload, UnionValuePayload, VariantValuePayload};
17use crate::schema::validation::subtyping::{is_assignable, is_equivalent_cross_graph};
18use crate::schema::validation::validate_value;
19use serde::{Deserialize, Serialize};
20use std::collections::{BTreeMap, HashMap, HashSet};
21
22mod compiler;
23mod equality;
24mod evaluator;
25
26pub use compiler::compile_tool_compatibility;
27pub use evaluator::apply_projection;
28
29const MAX_PROJECTION_NODES: usize = 4096;
30const MAX_PROJECTION_DEPTH: usize = 128;
31
32#[derive(
33 Clone,
34 Copy,
35 Debug,
36 Default,
37 PartialEq,
38 Eq,
39 Serialize,
40 Deserialize,
41 golem_schema_derive::IntoSchema,
42 golem_schema_derive::FromSchema,
43)]
44#[cfg_attr(
45 feature = "full",
46 derive(desert_rust::BinaryCodec, golem_schema_derive::PoemSchema)
47)]
48#[serde(rename_all = "kebab-case")]
49pub enum ToolCompatibilityMode {
50 StrictEquality,
51 #[default]
52 StructuralSubtype,
53 Nominal,
54}
55
56#[derive(
57 Clone,
58 Debug,
59 PartialEq,
60 Serialize,
61 Deserialize,
62 golem_schema_derive::IntoSchema,
63 golem_schema_derive::FromSchema,
64)]
65#[cfg_attr(
66 feature = "full",
67 derive(desert_rust::BinaryCodec, golem_schema_derive::PoemSchema)
68)]
69#[cfg_attr(feature = "full", desert(evolution()))]
70pub struct CompiledToolCompatibility {
71 pub mode: ToolCompatibilityMode,
72 pub commands: Vec<CompiledCommandCompatibility>,
73 pub warnings: Vec<ToolCompatibilityWarning>,
74}
75
76#[derive(
77 Clone,
78 Debug,
79 PartialEq,
80 Eq,
81 Serialize,
82 Deserialize,
83 golem_schema_derive::IntoSchema,
84 golem_schema_derive::FromSchema,
85)]
86#[cfg_attr(
87 feature = "full",
88 derive(desert_rust::BinaryCodec, golem_schema_derive::PoemSchema)
89)]
90#[cfg_attr(feature = "full", desert(evolution()))]
91pub struct ToolCompatibilityWarning {
92 pub path: String,
93 pub name: String,
94}
95
96#[derive(
97 Clone,
98 Debug,
99 PartialEq,
100 Serialize,
101 Deserialize,
102 golem_schema_derive::IntoSchema,
103 golem_schema_derive::FromSchema,
104)]
105#[cfg_attr(
106 feature = "full",
107 derive(desert_rust::BinaryCodec, golem_schema_derive::PoemSchema)
108)]
109#[cfg_attr(feature = "full", desert(evolution()))]
110pub struct CompiledCommandCompatibility {
111 pub expected_path: Vec<String>,
112 pub inner_path: Vec<String>,
113 pub input: ProjectionPlan,
114 pub result: Option<ProjectionPlan>,
115 pub errors: Vec<ErrorProjection>,
116 pub forward_unknown_errors: bool,
117}
118
119#[derive(
120 Clone,
121 Debug,
122 PartialEq,
123 Serialize,
124 Deserialize,
125 golem_schema_derive::IntoSchema,
126 golem_schema_derive::FromSchema,
127)]
128#[cfg_attr(
129 feature = "full",
130 derive(desert_rust::BinaryCodec, golem_schema_derive::PoemSchema)
131)]
132#[cfg_attr(feature = "full", desert(evolution()))]
133pub struct ProjectionPlan {
134 pub source_schema: SchemaGraph,
135 pub target_schema: SchemaGraph,
136 pub nodes: Vec<ProjectionNode>,
137 pub root: usize,
138}
139
140#[derive(
141 Clone,
142 Debug,
143 PartialEq,
144 Serialize,
145 Deserialize,
146 golem_schema_derive::IntoSchema,
147 golem_schema_derive::FromSchema,
148)]
149#[cfg_attr(
150 feature = "full",
151 derive(desert_rust::BinaryCodec, golem_schema_derive::PoemSchema)
152)]
153#[cfg_attr(feature = "full", desert(evolution()))]
154#[serde(tag = "kind", rename_all = "kebab-case")]
155pub enum ProjectionNode {
156 Identity,
157 DynamicChecked,
158 Record {
159 fields: Vec<RecordFieldProjection>,
160 discard: Vec<usize>,
161 },
162 Tuple {
163 elements: Vec<usize>,
164 },
165 List {
166 item: usize,
167 },
168 FixedList {
169 item: usize,
170 length: u32,
171 },
172 Map {
173 key: usize,
174 value: usize,
175 },
176 Option {
177 some: usize,
178 },
179 Result {
180 ok: Option<usize>,
181 err: Option<usize>,
182 },
183 Variant {
184 cases: Vec<CaseProjection>,
185 },
186 Enum {
187 cases: Vec<usize>,
189 },
190 Flags {
191 flags: Vec<usize>,
193 },
194 Union {
195 branches: Vec<CaseProjection>,
196 },
197 Stream {
198 item: Option<usize>,
199 },
200 Recursive {
201 node: usize,
202 },
203}
204
205#[derive(
206 Clone,
207 Debug,
208 PartialEq,
209 Serialize,
210 Deserialize,
211 golem_schema_derive::IntoSchema,
212 golem_schema_derive::FromSchema,
213)]
214#[cfg_attr(
215 feature = "full",
216 derive(desert_rust::BinaryCodec, golem_schema_derive::PoemSchema)
217)]
218#[cfg_attr(feature = "full", desert(evolution()))]
219pub struct RecordFieldProjection {
220 pub target_name: String,
221 pub source_index: Option<usize>,
222 pub plan: Option<usize>,
223 pub default: Option<SchemaValue>,
224}
225
226#[derive(
227 Clone,
228 Debug,
229 PartialEq,
230 Serialize,
231 Deserialize,
232 golem_schema_derive::IntoSchema,
233 golem_schema_derive::FromSchema,
234)]
235#[cfg_attr(
236 feature = "full",
237 derive(desert_rust::BinaryCodec, golem_schema_derive::PoemSchema)
238)]
239#[cfg_attr(feature = "full", desert(evolution()))]
240pub struct CaseProjection {
241 pub name: String,
242 pub source_index: usize,
243 pub target_index: usize,
244 pub payload: Option<usize>,
245}
246
247#[derive(
248 Clone,
249 Debug,
250 PartialEq,
251 Serialize,
252 Deserialize,
253 golem_schema_derive::IntoSchema,
254 golem_schema_derive::FromSchema,
255)]
256#[cfg_attr(
257 feature = "full",
258 derive(desert_rust::BinaryCodec, golem_schema_derive::PoemSchema)
259)]
260#[cfg_attr(feature = "full", desert(evolution()))]
261pub struct ErrorProjection {
262 pub name: String,
263 pub inner_index: usize,
264 pub expected_index: Option<usize>,
265 pub payload: Option<ProjectionPlan>,
266}
267
268#[derive(
269 Clone,
270 Debug,
271 PartialEq,
272 Eq,
273 Serialize,
274 Deserialize,
275 golem_schema_derive::IntoSchema,
276 golem_schema_derive::FromSchema,
277)]
278#[cfg_attr(
279 feature = "full",
280 derive(desert_rust::BinaryCodec, golem_schema_derive::PoemSchema)
281)]
282#[cfg_attr(feature = "full", desert(evolution()))]
283pub struct ToolCompatibilityError {
284 pub path: String,
285 pub message: String,
286}
287
288pub trait ProjectionStreamHandler {
293 fn project_stream(
294 &mut self,
295 stream: crate::schema::SchemaValueStream,
296 item_plan: Option<ProjectionPlan>,
297 ) -> Result<crate::schema::SchemaValueStream, String>;
298
299 fn discard_stream(&mut self, stream: crate::schema::SchemaValueStream);
300}
301
302fn resolve<'a>(g: &'a SchemaGraph, t: &'a SchemaType) -> (&'a SchemaType, Option<TypeId>) {
303 let mut x = t;
304 let mut id = None;
305 let mut seen = HashSet::new();
306 while let SchemaType::Ref { id: i, .. } = x {
307 if !seen.insert(i.clone()) {
308 break;
309 }
310 id = Some(i.clone());
311 match g.defs.iter().find(|def| def.id == *i) {
312 Some(def) => x = &def.body,
313 None => break,
314 }
315 }
316 (x, id)
317}
318fn graph_for(g: &SchemaGraph, t: &SchemaType) -> SchemaGraph {
319 SchemaGraph {
320 defs: crate::schema::graph::reachable_defs(g, t),
321 root: t.clone(),
322 }
323}
324fn err(path: impl Into<String>, message: impl Into<String>) -> ToolCompatibilityError {
325 ToolCompatibilityError {
326 path: path.into(),
327 message: message.into(),
328 }
329}
330
331#[cfg(test)]
332mod tests;