Skip to main content

relay_knowledge/domain/business/
query.rs

1use serde::{Deserialize, Serialize};
2
3use crate::domain::{CodeRepositorySelector, DomainError, FreshnessPolicy};
4
5/// Requested business projection slice.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum BusinessKnowledgeQueryKind {
9    Terms,
10    Mappings,
11    All,
12}
13
14impl BusinessKnowledgeQueryKind {
15    pub const fn as_str(self) -> &'static str {
16        match self {
17            Self::Terms => "terms",
18            Self::Mappings => "mappings",
19            Self::All => "all",
20        }
21    }
22}
23
24/// Repository and immutable-ref bound business knowledge request.
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub struct BusinessKnowledgeQueryRequest {
27    pub repository: CodeRepositorySelector,
28    #[serde(default, skip_serializing_if = "Option::is_none")]
29    pub domain: Option<String>,
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub query: Option<String>,
32    pub kind: BusinessKnowledgeQueryKind,
33    pub freshness_policy: FreshnessPolicy,
34    pub limit: usize,
35}
36
37impl BusinessKnowledgeQueryRequest {
38    pub fn new(
39        repository: CodeRepositorySelector,
40        domain: Option<String>,
41        query: Option<String>,
42        kind: BusinessKnowledgeQueryKind,
43        freshness_policy: FreshnessPolicy,
44        limit: usize,
45    ) -> Result<Self, DomainError> {
46        if !(1..=500).contains(&limit) {
47            return Err(DomainError::invalid("limit", "must be between 1 and 500"));
48        }
49        let domain = validate_optional("domain", domain, 128)?;
50        let query = validate_optional("query", query, 1_024)?;
51        Ok(Self {
52            repository,
53            domain,
54            query,
55            kind,
56            freshness_policy,
57            limit,
58        })
59    }
60}
61
62fn validate_optional(
63    field: &'static str,
64    value: Option<String>,
65    max_bytes: usize,
66) -> Result<Option<String>, DomainError> {
67    value
68        .map(|value| {
69            let value = value.trim();
70            if value.is_empty() {
71                return Err(DomainError::invalid(field, "must not be empty"));
72            }
73            if value.len() > max_bytes {
74                return Err(DomainError::invalid(
75                    field,
76                    format!("must be {max_bytes} bytes or less"),
77                ));
78            }
79            Ok(value.to_owned())
80        })
81        .transpose()
82}
83
84/// Term-name resolution state for a business query.
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
86#[serde(rename_all = "snake_case")]
87pub enum BusinessKnowledgeResolution {
88    List,
89    Exact,
90    Ambiguous,
91    NotFound,
92}
93
94#[cfg(test)]
95#[path = "query_tests.rs"]
96mod tests;