1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
5#[serde(transparent)]
6pub struct LandscapeTypeId(pub String);
7
8impl LandscapeTypeId {
9 pub fn new(value: impl Into<String>) -> Self {
10 Self(value.into())
11 }
12
13 pub fn as_str(&self) -> &str {
14 &self.0
15 }
16}
17
18impl From<&str> for LandscapeTypeId {
19 fn from(value: &str) -> Self {
20 Self(value.to_string())
21 }
22}
23
24impl From<String> for LandscapeTypeId {
25 fn from(value: String) -> Self {
26 Self(value)
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
32#[serde(transparent)]
33pub struct LandscapeFunctionId(pub String);
34
35impl LandscapeFunctionId {
36 pub fn new(value: impl Into<String>) -> Self {
37 Self(value.into())
38 }
39
40 pub fn as_str(&self) -> &str {
41 &self.0
42 }
43}
44
45impl From<&str> for LandscapeFunctionId {
46 fn from(value: &str) -> Self {
47 Self(value.to_string())
48 }
49}
50
51impl From<String> for LandscapeFunctionId {
52 fn from(value: String) -> Self {
53 Self(value)
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
59#[serde(rename_all = "camelCase")]
60pub struct LandscapeTypeRef {
61 pub id: LandscapeTypeId,
62 pub owner: String,
63 pub rust_type: Option<String>,
64 pub schema_ref: Option<String>,
65}
66
67impl LandscapeTypeRef {
68 pub fn new(id: impl Into<LandscapeTypeId>, owner: impl Into<String>) -> Self {
69 Self {
70 id: id.into(),
71 owner: owner.into(),
72 rust_type: None,
73 schema_ref: None,
74 }
75 }
76
77 pub fn rust_type(mut self, value: impl Into<String>) -> Self {
78 self.rust_type = Some(value.into());
79 self
80 }
81
82 pub fn schema_ref(mut self, value: impl Into<String>) -> Self {
83 self.schema_ref = Some(value.into());
84 self
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
90#[serde(rename_all = "camelCase")]
91pub struct LandscapePort {
92 pub name: String,
93 #[serde(rename = "typeRef")]
94 pub type_ref: LandscapeTypeRef,
95 pub required: bool,
96 pub cardinality: LandscapeCardinality,
97}
98
99impl LandscapePort {
100 pub fn new(name: impl Into<String>, type_ref: LandscapeTypeRef) -> Self {
101 Self {
102 name: name.into(),
103 type_ref,
104 required: true,
105 cardinality: LandscapeCardinality::One,
106 }
107 }
108
109 pub fn optional(mut self) -> Self {
110 self.required = false;
111 self.cardinality = LandscapeCardinality::Optional;
112 self
113 }
114
115 pub fn many(mut self) -> Self {
116 self.cardinality = LandscapeCardinality::Many;
117 self
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
123#[serde(rename_all = "camelCase")]
124pub enum LandscapeCardinality {
125 One,
126 Optional,
127 Many,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
132#[serde(rename_all = "camelCase")]
133pub struct LandscapeFunction {
134 pub id: LandscapeFunctionId,
135 pub owner: String,
136 pub inputs: Vec<LandscapePort>,
137 pub outputs: Vec<LandscapePort>,
138 pub stability: LandscapeStability,
139}
140
141impl LandscapeFunction {
142 pub fn new(id: impl Into<LandscapeFunctionId>, owner: impl Into<String>) -> Self {
143 Self {
144 id: id.into(),
145 owner: owner.into(),
146 inputs: Vec::new(),
147 outputs: Vec::new(),
148 stability: LandscapeStability::Stable,
149 }
150 }
151
152 pub fn input(mut self, port: LandscapePort) -> Self {
153 self.inputs.push(port);
154 self
155 }
156
157 pub fn output(mut self, port: LandscapePort) -> Self {
158 self.outputs.push(port);
159 self
160 }
161
162 pub fn stability(mut self, stability: LandscapeStability) -> Self {
163 self.stability = stability;
164 self
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
170#[serde(rename_all = "camelCase")]
171pub enum LandscapeStability {
172 Stable,
173 Experimental,
174 Internal,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
179#[serde(rename_all = "camelCase")]
180pub struct LandscapeOperationContract {
181 pub function: LandscapeFunction,
182}
183
184impl LandscapeOperationContract {
185 pub fn new(function: LandscapeFunction) -> Self {
186 Self { function }
187 }
188}
189
190pub fn known_owner_packages() -> &'static [&'static str] {
192 well_known::known_owner_packages()
193}
194
195pub fn validate_landscape_contract(contract: &LandscapeOperationContract) -> Result<(), String> {
197 validate_landscape_function(&contract.function)
198}
199
200pub fn validate_landscape_function(function: &LandscapeFunction) -> Result<(), String> {
202 if function.id.as_str().trim().is_empty() {
203 return Err("curated function id must not be empty".to_string());
204 }
205 validate_owner("curated function", &function.owner)?;
206 if !matches!(function.stability, LandscapeStability::Internal) {
207 if function.inputs.is_empty() {
208 return Err(format!(
209 "curated function `{}` must declare at least one input",
210 function.id.as_str()
211 ));
212 }
213 if function.outputs.is_empty() {
214 return Err(format!(
215 "curated function `{}` must declare at least one output",
216 function.id.as_str()
217 ));
218 }
219 }
220 for port in function.inputs.iter().chain(function.outputs.iter()) {
221 validate_port(function.id.as_str(), port)?;
222 }
223 Ok(())
224}
225
226fn validate_port(function_id: &str, port: &LandscapePort) -> Result<(), String> {
227 if port.name.trim().is_empty() {
228 return Err(format!(
229 "curated function `{function_id}` has a port with an empty name"
230 ));
231 }
232 if port.type_ref.id.as_str().trim().is_empty() {
233 return Err(format!(
234 "curated function `{function_id}` port `{}` has an empty type id",
235 port.name
236 ));
237 }
238 validate_owner(
239 &format!("curated function `{function_id}` port `{}`", port.name),
240 &port.type_ref.owner,
241 )
242}
243
244fn validate_owner(context: &str, owner: &str) -> Result<(), String> {
245 if owner.trim().is_empty() {
246 return Err(format!("{context} owner must not be empty"));
247 }
248 if !known_owner_packages().contains(&owner) {
249 return Err(format!("{context} owner `{owner}` is not known"));
250 }
251 Ok(())
252}
253
254pub mod well_known {
256 use super::{LandscapeTypeId, LandscapeTypeRef};
257
258 pub const OWNER_RUNTIME_CORE: &str = "moenarch-runtime-core";
259 pub const OWNER_TEXT_CORE: &str = "moenarch-text-core";
260 pub const OWNER_TEXT_TRANSCRIPTS: &str = "moenarch-text-transcripts";
261 pub const OWNER_TEXT_ANALYSIS: &str = "moenarch-text-analysis";
262 pub const OWNER_TEXT_RETRIEVAL: &str = "moenarch-text-retrieval";
263 pub const OWNER_IMAGE_ANALYSIS_CORE: &str = "moenarch-image-analysis-core";
264 pub const OWNER_IMAGE_ANALYSIS_DETECTION: &str = "moenarch-image-analysis-detection";
265 pub const OWNER_AUDIO_ANALYSIS_CORE: &str = "moenarch-audio-analysis-core";
266 pub const OWNER_AUDIO_ANALYSIS_TRANSCRIPTION: &str = "moenarch-audio-analysis-transcription";
267 pub const OWNER_VISION_CORE: &str = "moenarch-vision-core";
268 pub const OWNER_VECTOR_ANALYSIS_CORE: &str = "moenarch-vector-analysis-core";
269 pub const OWNER_TENSOR_DATA: &str = "moenarch-tensor-data";
270 pub const OWNER_NUMBERS_CORE: &str = "moenarch-numbers-core";
271 pub const OWNER_MATH_GEOMETRY_2D: &str = "moenarch-math-geometry-2d";
272 pub const OWNER_VIDEO_ANALYSIS_CORE: &str = "moenarch-video-analysis-core";
273 pub const OWNER_VIDEO_ANALYSIS_DETECTORS: &str = "moenarch-video-analysis-detectors";
274 pub const OWNER_VIDEO_ANALYSIS_OUTPUT: &str = "moenarch-video-analysis-output";
275 pub const OWNER_VIDEO_ANALYSIS_RECONSTRUCTION: &str = "moenarch-video-analysis-reconstruction";
276 pub const OWNER_VIDEO_ANALYSIS_SFM: &str = "moenarch-video-analysis-sfm";
277 pub const OWNER_VIDEO_ANALYSIS_RADIANCE_FIELDS: &str =
278 "moenarch-video-analysis-radiance-fields";
279 pub const OWNER_VIDEO_ANALYSIS_RADIANCE_PIPELINE: &str =
280 "moenarch-video-analysis-radiance-pipeline";
281
282 pub const RUNTIME_SURFACE_REQUEST: &str = "runtime.surfaceRequest";
283 pub const RUNTIME_SURFACE_RESPONSE: &str = "runtime.surfaceResponse";
284 pub const TEXT_DOCUMENT: &str = "text.document";
285 pub const TEXT_SEGMENT: &str = "text.segment";
286 pub const TEXT_TRANSCRIPT_SEGMENT: &str = "text.transcriptSegment";
287 pub const TEXT_ANALYSIS_REPORT: &str = "text.analysisReport";
288 pub const TEXT_RETRIEVAL_QUERY: &str = "text.retrievalQuery";
289 pub const TEXT_SEARCH_RESULT: &str = "text.searchResult";
290 pub const IMAGE_IMAGE: &str = "image.image";
291 pub const IMAGE_DETECTION_REQUEST: &str = "image.detectionRequest";
292 pub const AUDIO_FRAME: &str = "audio.frame";
293 pub const AUDIO_SOURCE: &str = "audio.source";
294 pub const AUDIO_TRANSCRIPTION_CONFIG: &str = "audio.transcriptionConfig";
295 pub const VISION_DETECTION: &str = "vision.detection";
296 pub const VISION_EMBEDDING: &str = "vision.embedding";
297 pub const VECTOR_VECTOR: &str = "vector.vector";
298 pub const TENSOR_F32_TENSOR: &str = "tensor.f32Tensor";
299 pub const NUMBERS_SUMMARY: &str = "numbers.summary";
300 pub const GEOMETRY_RECT_U32: &str = "geometry.rectU32";
301 pub const GEOMETRY_POINT2F: &str = "geometry.point2f";
302 pub const VIDEO_TIMECODE: &str = "video.timecode";
303 pub const VIDEO_FRAME: &str = "video.frame";
304 pub const VIDEO_SCENE: &str = "video.scene";
305 pub const VIDEO_SCENE_LIST: &str = "video.sceneList";
306 pub const VIDEO_DETECTOR_CONFIG: &str = "video.detectorConfig";
307 pub const VIDEO_CAMERA: &str = "video.camera";
308 pub const VIDEO_CAMERA_PATH: &str = "video.cameraPath";
309 pub const VIDEO_RECONSTRUCTION: &str = "video.reconstruction";
310 pub const VIDEO_SFM_MATCH_PLAN: &str = "video.sfmMatchPlan";
311 pub const VIDEO_RADIANCE_ASSET: &str = "video.radianceAsset";
312
313 pub fn runtime_surface_request() -> LandscapeTypeRef {
314 type_ref(
315 RUNTIME_SURFACE_REQUEST,
316 OWNER_RUNTIME_CORE,
317 "runtime_core::SurfaceRequest",
318 )
319 }
320
321 pub fn runtime_surface_response() -> LandscapeTypeRef {
322 type_ref(
323 RUNTIME_SURFACE_RESPONSE,
324 OWNER_RUNTIME_CORE,
325 "runtime_core::SurfaceResponse",
326 )
327 }
328
329 pub fn text_document() -> LandscapeTypeRef {
330 type_ref(
331 TEXT_DOCUMENT,
332 OWNER_TEXT_CORE,
333 "text_core::TextDocumentContract",
334 )
335 }
336
337 pub fn text_segment() -> LandscapeTypeRef {
338 type_ref(
339 TEXT_SEGMENT,
340 OWNER_TEXT_CORE,
341 "text_core::TextSegmentContract",
342 )
343 }
344
345 pub fn text_transcript_segment() -> LandscapeTypeRef {
346 type_ref(
347 TEXT_TRANSCRIPT_SEGMENT,
348 OWNER_TEXT_TRANSCRIPTS,
349 "text_transcripts::TranscriptSegmentContract",
350 )
351 }
352
353 pub fn text_analysis_report() -> LandscapeTypeRef {
354 type_ref(
355 TEXT_ANALYSIS_REPORT,
356 OWNER_TEXT_ANALYSIS,
357 "text_analysis::DocumentAnalysisReport",
358 )
359 }
360
361 pub fn text_retrieval_query() -> LandscapeTypeRef {
362 type_ref(
363 TEXT_RETRIEVAL_QUERY,
364 OWNER_TEXT_RETRIEVAL,
365 "text_retrieval::SearchQuery",
366 )
367 }
368
369 pub fn text_search_result() -> LandscapeTypeRef {
370 type_ref(
371 TEXT_SEARCH_RESULT,
372 OWNER_TEXT_RETRIEVAL,
373 "text_retrieval::SearchResult",
374 )
375 }
376
377 pub fn image_image() -> LandscapeTypeRef {
378 type_ref(
379 IMAGE_IMAGE,
380 OWNER_IMAGE_ANALYSIS_CORE,
381 "image_analysis_core::OwnedImage",
382 )
383 }
384
385 pub fn image_detection_request() -> LandscapeTypeRef {
386 type_ref(
387 IMAGE_DETECTION_REQUEST,
388 OWNER_IMAGE_ANALYSIS_DETECTION,
389 "image_analysis_detection::ImageDetectionRequest",
390 )
391 }
392
393 pub fn audio_frame() -> LandscapeTypeRef {
394 type_ref(
395 AUDIO_FRAME,
396 OWNER_AUDIO_ANALYSIS_CORE,
397 "video_analysis_core::OwnedAudioFrame",
398 )
399 }
400
401 pub fn audio_source() -> LandscapeTypeRef {
402 type_ref(
403 AUDIO_SOURCE,
404 OWNER_AUDIO_ANALYSIS_TRANSCRIPTION,
405 "audio_analysis_transcription::TranscriptionSource",
406 )
407 }
408
409 pub fn audio_transcription_config() -> LandscapeTypeRef {
410 type_ref(
411 AUDIO_TRANSCRIPTION_CONFIG,
412 OWNER_AUDIO_ANALYSIS_TRANSCRIPTION,
413 "audio_analysis_transcription::TranscriptionPipelineRequest",
414 )
415 }
416
417 pub fn vision_detection() -> LandscapeTypeRef {
418 type_ref(
419 VISION_DETECTION,
420 OWNER_VISION_CORE,
421 "vision_core::VisualDetection",
422 )
423 }
424
425 pub fn vision_embedding() -> LandscapeTypeRef {
426 type_ref(
427 VISION_EMBEDDING,
428 OWNER_VISION_CORE,
429 "vision_core::VisualEmbedding",
430 )
431 }
432
433 pub fn vector_vector() -> LandscapeTypeRef {
434 type_ref(
435 VECTOR_VECTOR,
436 OWNER_VECTOR_ANALYSIS_CORE,
437 "vector_analysis_core::DenseVector",
438 )
439 }
440
441 pub fn tensor_f32_tensor() -> LandscapeTypeRef {
442 type_ref(
443 TENSOR_F32_TENSOR,
444 OWNER_TENSOR_DATA,
445 "tensor_data::F32Tensor",
446 )
447 }
448
449 pub fn numbers_summary() -> LandscapeTypeRef {
450 type_ref(
451 NUMBERS_SUMMARY,
452 OWNER_NUMBERS_CORE,
453 "numbers_core::NumberSummary",
454 )
455 }
456
457 pub fn geometry_rect_u32() -> LandscapeTypeRef {
458 type_ref(
459 GEOMETRY_RECT_U32,
460 OWNER_MATH_GEOMETRY_2D,
461 "math_geometry_2d::RectU32",
462 )
463 }
464
465 pub fn geometry_point2f() -> LandscapeTypeRef {
466 type_ref(
467 GEOMETRY_POINT2F,
468 OWNER_MATH_GEOMETRY_2D,
469 "math_geometry_2d::Point2f",
470 )
471 }
472
473 pub fn video_timecode() -> LandscapeTypeRef {
474 type_ref(
475 VIDEO_TIMECODE,
476 OWNER_VIDEO_ANALYSIS_CORE,
477 "video_analysis_core::FrameTimecode",
478 )
479 }
480
481 pub fn video_frame() -> LandscapeTypeRef {
482 type_ref(
483 VIDEO_FRAME,
484 OWNER_VIDEO_ANALYSIS_CORE,
485 "video_analysis_core::VideoFrame",
486 )
487 }
488
489 pub fn video_scene() -> LandscapeTypeRef {
490 type_ref(
491 VIDEO_SCENE,
492 OWNER_VIDEO_ANALYSIS_CORE,
493 "video_analysis_core::Scene",
494 )
495 }
496
497 pub fn video_scene_list() -> LandscapeTypeRef {
498 type_ref(
499 VIDEO_SCENE_LIST,
500 OWNER_VIDEO_ANALYSIS_OUTPUT,
501 "video_analysis_output::SceneList",
502 )
503 }
504
505 pub fn video_detector_config() -> LandscapeTypeRef {
506 type_ref(
507 VIDEO_DETECTOR_CONFIG,
508 OWNER_VIDEO_ANALYSIS_DETECTORS,
509 "video_analysis_detectors::WeightedCompositeDetector",
510 )
511 }
512
513 pub fn video_camera() -> LandscapeTypeRef {
514 type_ref(
515 VIDEO_CAMERA,
516 OWNER_VIDEO_ANALYSIS_RADIANCE_FIELDS,
517 "video_analysis_radiance_fields::CameraView",
518 )
519 }
520
521 pub fn video_camera_path() -> LandscapeTypeRef {
522 type_ref(
523 VIDEO_CAMERA_PATH,
524 OWNER_VIDEO_ANALYSIS_RADIANCE_FIELDS,
525 "video_analysis_radiance_fields::CameraPath",
526 )
527 }
528
529 pub fn video_reconstruction() -> LandscapeTypeRef {
530 type_ref(
531 VIDEO_RECONSTRUCTION,
532 OWNER_VIDEO_ANALYSIS_RECONSTRUCTION,
533 "video_analysis_reconstruction::Reconstruction",
534 )
535 }
536
537 pub fn video_sfm_match_plan() -> LandscapeTypeRef {
538 type_ref(
539 VIDEO_SFM_MATCH_PLAN,
540 OWNER_VIDEO_ANALYSIS_SFM,
541 "video_analysis_sfm::SfmRequest",
542 )
543 }
544
545 pub fn video_radiance_asset() -> LandscapeTypeRef {
546 type_ref(
547 VIDEO_RADIANCE_ASSET,
548 OWNER_VIDEO_ANALYSIS_RADIANCE_PIPELINE,
549 "video_analysis_radiance_pipeline::RadianceAsset",
550 )
551 }
552
553 pub fn known_owner_packages() -> &'static [&'static str] {
554 &[
555 OWNER_RUNTIME_CORE,
556 OWNER_TEXT_CORE,
557 OWNER_TEXT_TRANSCRIPTS,
558 OWNER_TEXT_ANALYSIS,
559 OWNER_TEXT_RETRIEVAL,
560 OWNER_IMAGE_ANALYSIS_CORE,
561 OWNER_IMAGE_ANALYSIS_DETECTION,
562 OWNER_AUDIO_ANALYSIS_CORE,
563 OWNER_AUDIO_ANALYSIS_TRANSCRIPTION,
564 OWNER_VISION_CORE,
565 OWNER_VECTOR_ANALYSIS_CORE,
566 OWNER_TENSOR_DATA,
567 OWNER_NUMBERS_CORE,
568 OWNER_MATH_GEOMETRY_2D,
569 OWNER_VIDEO_ANALYSIS_CORE,
570 OWNER_VIDEO_ANALYSIS_DETECTORS,
571 OWNER_VIDEO_ANALYSIS_OUTPUT,
572 OWNER_VIDEO_ANALYSIS_RECONSTRUCTION,
573 OWNER_VIDEO_ANALYSIS_SFM,
574 OWNER_VIDEO_ANALYSIS_RADIANCE_FIELDS,
575 OWNER_VIDEO_ANALYSIS_RADIANCE_PIPELINE,
576 ]
577 }
578
579 pub fn known_type_ids() -> &'static [&'static str] {
580 &[
581 RUNTIME_SURFACE_REQUEST,
582 RUNTIME_SURFACE_RESPONSE,
583 TEXT_DOCUMENT,
584 TEXT_SEGMENT,
585 TEXT_TRANSCRIPT_SEGMENT,
586 TEXT_ANALYSIS_REPORT,
587 TEXT_RETRIEVAL_QUERY,
588 TEXT_SEARCH_RESULT,
589 IMAGE_IMAGE,
590 IMAGE_DETECTION_REQUEST,
591 AUDIO_FRAME,
592 AUDIO_SOURCE,
593 AUDIO_TRANSCRIPTION_CONFIG,
594 VISION_DETECTION,
595 VISION_EMBEDDING,
596 VECTOR_VECTOR,
597 TENSOR_F32_TENSOR,
598 NUMBERS_SUMMARY,
599 GEOMETRY_RECT_U32,
600 GEOMETRY_POINT2F,
601 VIDEO_TIMECODE,
602 VIDEO_FRAME,
603 VIDEO_SCENE,
604 VIDEO_SCENE_LIST,
605 VIDEO_DETECTOR_CONFIG,
606 VIDEO_CAMERA,
607 VIDEO_CAMERA_PATH,
608 VIDEO_RECONSTRUCTION,
609 VIDEO_SFM_MATCH_PLAN,
610 VIDEO_RADIANCE_ASSET,
611 ]
612 }
613
614 fn type_ref(
615 id: &'static str,
616 owner: &'static str,
617 rust_type: &'static str,
618 ) -> LandscapeTypeRef {
619 LandscapeTypeRef::new(LandscapeTypeId::new(id), owner).rust_type(rust_type)
620 }
621}