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 = "moritzbrantner-runtime-core";
259 pub const OWNER_TEXT_CORE: &str = "moritzbrantner-text-core";
260 pub const OWNER_TEXT_TRANSCRIPTS: &str = "moritzbrantner-text-transcripts";
261 pub const OWNER_TEXT_ANALYSIS: &str = "moritzbrantner-text-analysis";
262 pub const OWNER_TEXT_RETRIEVAL: &str = "moritzbrantner-text-retrieval";
263 pub const OWNER_IMAGE_ANALYSIS_CORE: &str = "moritzbrantner-image-analysis-core";
264 pub const OWNER_IMAGE_ANALYSIS_DETECTION: &str = "moritzbrantner-image-analysis-detection";
265 pub const OWNER_AUDIO_ANALYSIS_CORE: &str = "moritzbrantner-audio-analysis-core";
266 pub const OWNER_AUDIO_ANALYSIS_TRANSCRIPTION: &str =
267 "moritzbrantner-audio-analysis-transcription";
268 pub const OWNER_VISION_CORE: &str = "moritzbrantner-vision-core";
269 pub const OWNER_VECTOR_ANALYSIS_CORE: &str = "moritzbrantner-vector-analysis-core";
270 pub const OWNER_TENSOR_DATA: &str = "moritzbrantner-tensor-data";
271 pub const OWNER_NUMBERS_CORE: &str = "moritzbrantner-numbers-core";
272 pub const OWNER_MATH_GEOMETRY_2D: &str = "moritzbrantner-math-geometry-2d";
273 pub const OWNER_VIDEO_ANALYSIS_CORE: &str = "moritzbrantner-video-analysis-core";
274 pub const OWNER_VIDEO_ANALYSIS_DETECTORS: &str = "moritzbrantner-video-analysis-detectors";
275 pub const OWNER_VIDEO_ANALYSIS_OUTPUT: &str = "moritzbrantner-video-analysis-output";
276 pub const OWNER_VIDEO_ANALYSIS_RECONSTRUCTION: &str =
277 "moritzbrantner-video-analysis-reconstruction";
278 pub const OWNER_VIDEO_ANALYSIS_SFM: &str = "moritzbrantner-video-analysis-sfm";
279 pub const OWNER_VIDEO_ANALYSIS_RADIANCE_FIELDS: &str =
280 "moritzbrantner-video-analysis-radiance-fields";
281 pub const OWNER_VIDEO_ANALYSIS_RADIANCE_PIPELINE: &str =
282 "moritzbrantner-video-analysis-radiance-pipeline";
283
284 pub const RUNTIME_SURFACE_REQUEST: &str = "runtime.surfaceRequest";
285 pub const RUNTIME_SURFACE_RESPONSE: &str = "runtime.surfaceResponse";
286 pub const TEXT_DOCUMENT: &str = "text.document";
287 pub const TEXT_SEGMENT: &str = "text.segment";
288 pub const TEXT_TRANSCRIPT_SEGMENT: &str = "text.transcriptSegment";
289 pub const TEXT_ANALYSIS_REPORT: &str = "text.analysisReport";
290 pub const TEXT_RETRIEVAL_QUERY: &str = "text.retrievalQuery";
291 pub const TEXT_SEARCH_RESULT: &str = "text.searchResult";
292 pub const IMAGE_IMAGE: &str = "image.image";
293 pub const IMAGE_DETECTION_REQUEST: &str = "image.detectionRequest";
294 pub const AUDIO_FRAME: &str = "audio.frame";
295 pub const AUDIO_SOURCE: &str = "audio.source";
296 pub const AUDIO_TRANSCRIPTION_CONFIG: &str = "audio.transcriptionConfig";
297 pub const VISION_DETECTION: &str = "vision.detection";
298 pub const VISION_EMBEDDING: &str = "vision.embedding";
299 pub const VECTOR_VECTOR: &str = "vector.vector";
300 pub const TENSOR_F32_TENSOR: &str = "tensor.f32Tensor";
301 pub const NUMBERS_SUMMARY: &str = "numbers.summary";
302 pub const GEOMETRY_RECT_U32: &str = "geometry.rectU32";
303 pub const GEOMETRY_POINT2F: &str = "geometry.point2f";
304 pub const VIDEO_TIMECODE: &str = "video.timecode";
305 pub const VIDEO_FRAME: &str = "video.frame";
306 pub const VIDEO_SCENE: &str = "video.scene";
307 pub const VIDEO_SCENE_LIST: &str = "video.sceneList";
308 pub const VIDEO_DETECTOR_CONFIG: &str = "video.detectorConfig";
309 pub const VIDEO_CAMERA: &str = "video.camera";
310 pub const VIDEO_CAMERA_PATH: &str = "video.cameraPath";
311 pub const VIDEO_RECONSTRUCTION: &str = "video.reconstruction";
312 pub const VIDEO_SFM_MATCH_PLAN: &str = "video.sfmMatchPlan";
313 pub const VIDEO_RADIANCE_ASSET: &str = "video.radianceAsset";
314
315 pub fn runtime_surface_request() -> LandscapeTypeRef {
316 type_ref(
317 RUNTIME_SURFACE_REQUEST,
318 OWNER_RUNTIME_CORE,
319 "runtime_core::SurfaceRequest",
320 )
321 }
322
323 pub fn runtime_surface_response() -> LandscapeTypeRef {
324 type_ref(
325 RUNTIME_SURFACE_RESPONSE,
326 OWNER_RUNTIME_CORE,
327 "runtime_core::SurfaceResponse",
328 )
329 }
330
331 pub fn text_document() -> LandscapeTypeRef {
332 type_ref(
333 TEXT_DOCUMENT,
334 OWNER_TEXT_CORE,
335 "text_core::TextDocumentContract",
336 )
337 }
338
339 pub fn text_segment() -> LandscapeTypeRef {
340 type_ref(
341 TEXT_SEGMENT,
342 OWNER_TEXT_CORE,
343 "text_core::TextSegmentContract",
344 )
345 }
346
347 pub fn text_transcript_segment() -> LandscapeTypeRef {
348 type_ref(
349 TEXT_TRANSCRIPT_SEGMENT,
350 OWNER_TEXT_TRANSCRIPTS,
351 "text_transcripts::TranscriptSegmentContract",
352 )
353 }
354
355 pub fn text_analysis_report() -> LandscapeTypeRef {
356 type_ref(
357 TEXT_ANALYSIS_REPORT,
358 OWNER_TEXT_ANALYSIS,
359 "text_analysis::DocumentAnalysisReport",
360 )
361 }
362
363 pub fn text_retrieval_query() -> LandscapeTypeRef {
364 type_ref(
365 TEXT_RETRIEVAL_QUERY,
366 OWNER_TEXT_RETRIEVAL,
367 "text_retrieval::SearchQuery",
368 )
369 }
370
371 pub fn text_search_result() -> LandscapeTypeRef {
372 type_ref(
373 TEXT_SEARCH_RESULT,
374 OWNER_TEXT_RETRIEVAL,
375 "text_retrieval::SearchResult",
376 )
377 }
378
379 pub fn image_image() -> LandscapeTypeRef {
380 type_ref(
381 IMAGE_IMAGE,
382 OWNER_IMAGE_ANALYSIS_CORE,
383 "image_analysis_core::OwnedImage",
384 )
385 }
386
387 pub fn image_detection_request() -> LandscapeTypeRef {
388 type_ref(
389 IMAGE_DETECTION_REQUEST,
390 OWNER_IMAGE_ANALYSIS_DETECTION,
391 "image_analysis_detection::ImageDetectionRequest",
392 )
393 }
394
395 pub fn audio_frame() -> LandscapeTypeRef {
396 type_ref(
397 AUDIO_FRAME,
398 OWNER_AUDIO_ANALYSIS_CORE,
399 "video_analysis_core::OwnedAudioFrame",
400 )
401 }
402
403 pub fn audio_source() -> LandscapeTypeRef {
404 type_ref(
405 AUDIO_SOURCE,
406 OWNER_AUDIO_ANALYSIS_TRANSCRIPTION,
407 "audio_analysis_transcription::TranscriptionSource",
408 )
409 }
410
411 pub fn audio_transcription_config() -> LandscapeTypeRef {
412 type_ref(
413 AUDIO_TRANSCRIPTION_CONFIG,
414 OWNER_AUDIO_ANALYSIS_TRANSCRIPTION,
415 "audio_analysis_transcription::TranscriptionPipelineRequest",
416 )
417 }
418
419 pub fn vision_detection() -> LandscapeTypeRef {
420 type_ref(
421 VISION_DETECTION,
422 OWNER_VISION_CORE,
423 "vision_core::VisualDetection",
424 )
425 }
426
427 pub fn vision_embedding() -> LandscapeTypeRef {
428 type_ref(
429 VISION_EMBEDDING,
430 OWNER_VISION_CORE,
431 "vision_core::VisualEmbedding",
432 )
433 }
434
435 pub fn vector_vector() -> LandscapeTypeRef {
436 type_ref(
437 VECTOR_VECTOR,
438 OWNER_VECTOR_ANALYSIS_CORE,
439 "vector_analysis_core::DenseVector",
440 )
441 }
442
443 pub fn tensor_f32_tensor() -> LandscapeTypeRef {
444 type_ref(
445 TENSOR_F32_TENSOR,
446 OWNER_TENSOR_DATA,
447 "tensor_data::F32Tensor",
448 )
449 }
450
451 pub fn numbers_summary() -> LandscapeTypeRef {
452 type_ref(
453 NUMBERS_SUMMARY,
454 OWNER_NUMBERS_CORE,
455 "numbers_core::NumberSummary",
456 )
457 }
458
459 pub fn geometry_rect_u32() -> LandscapeTypeRef {
460 type_ref(
461 GEOMETRY_RECT_U32,
462 OWNER_MATH_GEOMETRY_2D,
463 "math_geometry_2d::RectU32",
464 )
465 }
466
467 pub fn geometry_point2f() -> LandscapeTypeRef {
468 type_ref(
469 GEOMETRY_POINT2F,
470 OWNER_MATH_GEOMETRY_2D,
471 "math_geometry_2d::Point2f",
472 )
473 }
474
475 pub fn video_timecode() -> LandscapeTypeRef {
476 type_ref(
477 VIDEO_TIMECODE,
478 OWNER_VIDEO_ANALYSIS_CORE,
479 "video_analysis_core::FrameTimecode",
480 )
481 }
482
483 pub fn video_frame() -> LandscapeTypeRef {
484 type_ref(
485 VIDEO_FRAME,
486 OWNER_VIDEO_ANALYSIS_CORE,
487 "video_analysis_core::VideoFrame",
488 )
489 }
490
491 pub fn video_scene() -> LandscapeTypeRef {
492 type_ref(
493 VIDEO_SCENE,
494 OWNER_VIDEO_ANALYSIS_CORE,
495 "video_analysis_core::Scene",
496 )
497 }
498
499 pub fn video_scene_list() -> LandscapeTypeRef {
500 type_ref(
501 VIDEO_SCENE_LIST,
502 OWNER_VIDEO_ANALYSIS_OUTPUT,
503 "video_analysis_output::SceneList",
504 )
505 }
506
507 pub fn video_detector_config() -> LandscapeTypeRef {
508 type_ref(
509 VIDEO_DETECTOR_CONFIG,
510 OWNER_VIDEO_ANALYSIS_DETECTORS,
511 "video_analysis_detectors::WeightedCompositeDetector",
512 )
513 }
514
515 pub fn video_camera() -> LandscapeTypeRef {
516 type_ref(
517 VIDEO_CAMERA,
518 OWNER_VIDEO_ANALYSIS_RADIANCE_FIELDS,
519 "video_analysis_radiance_fields::CameraView",
520 )
521 }
522
523 pub fn video_camera_path() -> LandscapeTypeRef {
524 type_ref(
525 VIDEO_CAMERA_PATH,
526 OWNER_VIDEO_ANALYSIS_RADIANCE_FIELDS,
527 "video_analysis_radiance_fields::CameraPath",
528 )
529 }
530
531 pub fn video_reconstruction() -> LandscapeTypeRef {
532 type_ref(
533 VIDEO_RECONSTRUCTION,
534 OWNER_VIDEO_ANALYSIS_RECONSTRUCTION,
535 "video_analysis_reconstruction::Reconstruction",
536 )
537 }
538
539 pub fn video_sfm_match_plan() -> LandscapeTypeRef {
540 type_ref(
541 VIDEO_SFM_MATCH_PLAN,
542 OWNER_VIDEO_ANALYSIS_SFM,
543 "video_analysis_sfm::SfmRequest",
544 )
545 }
546
547 pub fn video_radiance_asset() -> LandscapeTypeRef {
548 type_ref(
549 VIDEO_RADIANCE_ASSET,
550 OWNER_VIDEO_ANALYSIS_RADIANCE_PIPELINE,
551 "video_analysis_radiance_pipeline::RadianceAsset",
552 )
553 }
554
555 pub fn known_owner_packages() -> &'static [&'static str] {
556 &[
557 OWNER_RUNTIME_CORE,
558 OWNER_TEXT_CORE,
559 OWNER_TEXT_TRANSCRIPTS,
560 OWNER_TEXT_ANALYSIS,
561 OWNER_TEXT_RETRIEVAL,
562 OWNER_IMAGE_ANALYSIS_CORE,
563 OWNER_IMAGE_ANALYSIS_DETECTION,
564 OWNER_AUDIO_ANALYSIS_CORE,
565 OWNER_AUDIO_ANALYSIS_TRANSCRIPTION,
566 OWNER_VISION_CORE,
567 OWNER_VECTOR_ANALYSIS_CORE,
568 OWNER_TENSOR_DATA,
569 OWNER_NUMBERS_CORE,
570 OWNER_MATH_GEOMETRY_2D,
571 OWNER_VIDEO_ANALYSIS_CORE,
572 OWNER_VIDEO_ANALYSIS_DETECTORS,
573 OWNER_VIDEO_ANALYSIS_OUTPUT,
574 OWNER_VIDEO_ANALYSIS_RECONSTRUCTION,
575 OWNER_VIDEO_ANALYSIS_SFM,
576 OWNER_VIDEO_ANALYSIS_RADIANCE_FIELDS,
577 OWNER_VIDEO_ANALYSIS_RADIANCE_PIPELINE,
578 ]
579 }
580
581 pub fn known_type_ids() -> &'static [&'static str] {
582 &[
583 RUNTIME_SURFACE_REQUEST,
584 RUNTIME_SURFACE_RESPONSE,
585 TEXT_DOCUMENT,
586 TEXT_SEGMENT,
587 TEXT_TRANSCRIPT_SEGMENT,
588 TEXT_ANALYSIS_REPORT,
589 TEXT_RETRIEVAL_QUERY,
590 TEXT_SEARCH_RESULT,
591 IMAGE_IMAGE,
592 IMAGE_DETECTION_REQUEST,
593 AUDIO_FRAME,
594 AUDIO_SOURCE,
595 AUDIO_TRANSCRIPTION_CONFIG,
596 VISION_DETECTION,
597 VISION_EMBEDDING,
598 VECTOR_VECTOR,
599 TENSOR_F32_TENSOR,
600 NUMBERS_SUMMARY,
601 GEOMETRY_RECT_U32,
602 GEOMETRY_POINT2F,
603 VIDEO_TIMECODE,
604 VIDEO_FRAME,
605 VIDEO_SCENE,
606 VIDEO_SCENE_LIST,
607 VIDEO_DETECTOR_CONFIG,
608 VIDEO_CAMERA,
609 VIDEO_CAMERA_PATH,
610 VIDEO_RECONSTRUCTION,
611 VIDEO_SFM_MATCH_PLAN,
612 VIDEO_RADIANCE_ASSET,
613 ]
614 }
615
616 fn type_ref(
617 id: &'static str,
618 owner: &'static str,
619 rust_type: &'static str,
620 ) -> LandscapeTypeRef {
621 LandscapeTypeRef::new(LandscapeTypeId::new(id), owner).rust_type(rust_type)
622 }
623}