1use rustev_contract::decimal::Decimal;
8use rustev_contract::definition::{
9 Adjustment, Cond, Definition, Expr, FieldDecl, Freshness, Handler, HandlerAction, InputDecl,
10 LimitsDecl, Literal, OutcomeDecl, ParamDecl, ParamSource, PolicyDecl, Predicate,
11 ProvenanceClass, ReasonSet, Rule, SemanticDecl, StepBody, StepDecl, TypeDecl,
12};
13use rustev_contract::definition::{CmpOp, ExactDecl};
14use rustev_contract::schema;
15
16use crate::ops::OpArgs;
17
18#[derive(Debug, Clone)]
20pub struct DefinitionBuilder {
21 name: String,
22 version: String,
23 package: String,
24 inputs: Vec<InputDecl>,
25 steps: Vec<StepDecl>,
26 policy: PolicyDecl,
27 limits: Option<LimitsDecl>,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct MissingLimits;
33
34impl DefinitionBuilder {
35 pub fn new(name: &str, version: &str, package: &str) -> Self {
36 Self {
37 name: name.into(),
38 version: version.into(),
39 package: package.into(),
40 inputs: vec![],
41 steps: vec![],
42 policy: PolicyDecl {
43 on_unresolved: vec![],
44 rules: vec![],
45 adjustments: vec![],
46 },
47 limits: None,
48 }
49 }
50
51 pub fn input(
52 mut self,
53 name: &str,
54 ty: TypeDecl,
55 provenance: &[ProvenanceClass],
56 freshness: Freshness,
57 ) -> Self {
58 self.inputs.push(InputDecl {
59 name: name.into(),
60 ty,
61 provenance: provenance.to_vec(),
62 freshness,
63 evidence: None,
64 on_disagreement: None,
65 });
66 self
67 }
68
69 pub fn exact(mut self, id: &str, args: OpArgs) -> Self {
70 let (op, version) = args.op();
71 self.steps.push(StepDecl {
72 id: id.into(),
73 body: StepBody::Exact(ExactDecl {
74 op: op.into(),
75 version,
76 args: args.to_json(),
77 }),
78 });
79 self
80 }
81
82 pub fn semantic(mut self, id: &str, decl: SemanticDecl) -> Self {
83 self.steps.push(StepDecl {
84 id: id.into(),
85 body: StepBody::Semantic(decl),
86 });
87 self
88 }
89
90 pub fn on_unresolved(
91 mut self,
92 target: &str,
93 reasons: ReasonSet,
94 action: HandlerAction,
95 ) -> Self {
96 self.policy.on_unresolved.push(Handler {
97 target: target.into(),
98 reasons,
99 action,
100 });
101 self
102 }
103
104 pub fn rule(mut self, id: &str, when: Cond, then: OutcomeDecl) -> Self {
105 self.policy.rules.push(Rule {
106 id: id.into(),
107 when,
108 then,
109 });
110 self
111 }
112
113 pub fn adjustment(mut self, id: &str, when: Cond, param: &str, scale: &[&str]) -> Self {
114 self.policy.adjustments.push(Adjustment {
115 id: id.into(),
116 when,
117 param: param.into(),
118 scale: scale.iter().map(|s| s.to_string()).collect(),
119 });
120 self
121 }
122
123 pub fn limits(mut self, limits: LimitsDecl) -> Self {
124 self.limits = Some(limits);
125 self
126 }
127
128 pub fn build(self) -> Result<Definition, MissingLimits> {
129 Ok(Definition {
130 schema: schema::DEFINITION.into(),
131 name: self.name,
132 version: self.version,
133 package: self.package,
134 inputs: self.inputs,
135 steps: self.steps,
136 policy: self.policy,
137 limits: self.limits.ok_or(MissingLimits)?,
138 actionability: None,
139 screening: None,
140 })
141 }
142}
143
144pub mod ty {
146 use super::*;
147
148 pub fn text(max_bytes: u64) -> TypeDecl {
149 TypeDecl::Text { max_bytes }
150 }
151 pub fn enumeration(options: &[&str]) -> TypeDecl {
152 TypeDecl::Enum {
153 options: options.iter().map(|s| s.to_string()).collect(),
154 }
155 }
156 pub fn list(item: TypeDecl, max_items: u64) -> TypeDecl {
157 TypeDecl::List {
158 item: Box::new(item),
159 max_items,
160 }
161 }
162 pub fn record(fields: &[(&str, TypeDecl)]) -> TypeDecl {
163 TypeDecl::Record {
164 fields: fields
165 .iter()
166 .map(|(n, t)| FieldDecl {
167 name: n.to_string(),
168 ty: t.clone(),
169 })
170 .collect(),
171 present_when: None,
172 }
173 }
174}
175
176pub mod e {
178 use super::*;
179
180 pub fn r(reference: &str) -> Expr {
181 Expr::Ref(reference.into())
182 }
183 pub fn int(v: i64) -> Expr {
184 Expr::Lit(Literal::Integer(v))
185 }
186 pub fn dec(v: &str) -> Expr {
189 Expr::Lit(Literal::Decimal(decimal(v)))
190 }
191 pub fn enum_lit(v: &str) -> Expr {
192 Expr::Lit(Literal::Enum(v.into()))
193 }
194 pub fn text(v: &str) -> Expr {
195 Expr::Lit(Literal::Text(v.into()))
196 }
197 pub fn boolean(v: bool) -> Expr {
198 Expr::Lit(Literal::Bool(v))
199 }
200 pub fn duration_ms(v: u64) -> Expr {
201 Expr::Lit(Literal::DurationMs(v))
202 }
203 pub fn sub(a: Expr, b: Expr) -> Expr {
204 Expr::Sub {
205 left: Box::new(a),
206 right: Box::new(b),
207 }
208 }
209 pub fn fx(amount: Expr, from: Expr, to: Expr, rates: &str, scale: u32) -> Expr {
210 Expr::Fx {
211 amount: Box::new(amount),
212 from: Box::new(from),
213 to: Box::new(to),
214 rates: rates.into(),
215 scale,
216 }
217 }
218 pub fn cmp(a: Expr, op: CmpOp, b: Expr) -> Predicate {
219 Predicate::Cmp {
220 left: a,
221 op,
222 right: b,
223 }
224 }
225 pub fn all(ps: Vec<Predicate>) -> Predicate {
226 Predicate::All(ps)
227 }
228 pub fn any(ps: Vec<Predicate>) -> Predicate {
229 Predicate::Any(ps)
230 }
231 pub fn not(p: Predicate) -> Predicate {
232 Predicate::Not(Box::new(p))
233 }
234 pub fn decimal(v: &str) -> Decimal {
236 Decimal::parse(v).expect("decimal literal")
237 }
238}
239
240pub mod out {
242 use super::*;
243
244 pub fn propose(action: &str, params: &[(&str, ParamSource)]) -> OutcomeDecl {
245 OutcomeDecl::Propose {
246 action: action.into(),
247 params: params
248 .iter()
249 .map(|(n, v)| ParamDecl {
250 name: n.to_string(),
251 value: v.clone(),
252 })
253 .collect(),
254 }
255 }
256 pub fn lit_enum(v: &str) -> ParamSource {
257 ParamSource::Lit(Literal::Enum(v.into()))
258 }
259 pub fn from(reference: &str) -> ParamSource {
260 ParamSource::Ref(reference.into())
261 }
262 pub fn escalate(reason: &str) -> OutcomeDecl {
263 OutcomeDecl::Escalate {
264 reason: reason.into(),
265 }
266 }
267}
268
269#[cfg(feature = "parameterized")]
272#[derive(Debug, Clone)]
273pub struct TemplateBuilder {
274 document: serde_json::Value,
275 error: Option<String>,
276}
277#[cfg(feature = "parameterized")]
278impl TemplateBuilder {
279 pub fn new(builder: DefinitionBuilder) -> Result<Self, MissingLimits> {
280 Ok(Self::from_definition(builder.build()?))
281 }
282 pub fn from_definition(definition: Definition) -> Self {
283 let mut document = serde_json::to_value(definition).expect("definition is serializable");
284 document["schema"] = serde_json::json!(schema::DEFINITION_V2);
285 Self {
286 document,
287 error: None,
288 }
289 }
290 pub fn parameter(mut self, declaration: rustev_contract::parameter::ParameterDecl) -> Self {
291 let array = self
292 .document
293 .as_object_mut()
294 .expect("definition object")
295 .entry("parameters")
296 .or_insert_with(|| serde_json::json!([]));
297 array
298 .as_array_mut()
299 .expect("parameter array")
300 .push(serde_json::json!(declaration));
301 self
302 }
303 pub fn variants(mut self, names: &[&str]) -> Self {
304 self.document["variants"] = serde_json::json!(names);
305 self
306 }
307 pub fn only_in(self, item_pointer: &str, names: &[&str]) -> Self {
308 self.item_member(item_pointer, "only_in", serde_json::json!(names))
309 }
310 pub fn enabled(self, item_pointer: &str, enabled: rustev_contract::parameter::Enabled) -> Self {
311 self.item_member(item_pointer, "enabled", serde_json::json!(enabled))
312 }
313 fn item_member(mut self, pointer: &str, key: &str, value: serde_json::Value) -> Self {
314 if let Some(item) = self
315 .document
316 .pointer_mut(pointer)
317 .and_then(serde_json::Value::as_object_mut)
318 {
319 item.insert(key.into(), value);
320 } else if self.error.is_none() {
321 self.error = Some(format!("no item at {pointer}"));
322 }
323 self
324 }
325 pub fn reference(mut self, pointer: &str, parameter: &str) -> Self {
327 if let Some(site) = self.document.pointer_mut(pointer) {
328 *site = serde_json::json!({"param": parameter});
329 } else if self.error.is_none() {
330 self.error = Some(format!("no reference site at {pointer}"));
331 }
332 self
333 }
334 pub fn build(self) -> Result<Vec<u8>, crate::instantiate::InstantiationRefusal> {
335 use crate::instantiate::{
336 InstantiationCode, InstantiationRefusal, check_declarations, parse_template,
337 };
338 let invalid = |detail: String| InstantiationRefusal {
339 stage: 1,
340 code: InstantiationCode::InvalidDefinitionDocument,
341 subject: format!(
342 "definition:{}@{}",
343 self.document["name"].as_str().unwrap_or("?"),
344 self.document["version"].as_str().unwrap_or("?")
345 ),
346 detail,
347 };
348 if let Some(e) = self.error {
349 return Err(invalid(e));
350 }
351 let bytes = rustev_contract::canonical::canonical_bytes(&self.document)
352 .map_err(|e| invalid(e.to_string()))?;
353 let template = parse_template(&bytes)?;
354 check_declarations(&template)?;
355 Ok(template.canonical)
356 }
357}