reasoninglayer 0.2.1

Rust client SDK for the Reasoning Layer API
Documentation
//! Sort (type hierarchy) operations.

use std::sync::Arc;

use crate::error::Error;
use crate::http::HttpClient;
use crate::types::common::RequestOptions;
use crate::types::sorts::{
    ApproveLearnedSimilarityRequest, ApproveLearnedSimilarityResponse, BulkCreateSortsRequest,
    BulkCreateSortsResponse, BulkSetSimilaritiesRequest, BulkSetSimilaritiesResponse,
    CreateSortRequest, DecodeGlbResponse, GetEquivalenceClassesResponse, GetPreorderDegreeRequest,
    GetPreorderDegreeResponse, GetSortSimilarityRequest, GetSortSimilarityResponse, GlbRequest,
    GlbResponse, IsSubtypeResponse, LearnSortSimilaritiesRequest, LearnSortSimilaritiesResponse,
    LearnedSimilarityListResponse, LubRequest, LubResponse, RejectLearnedSimilarityRequest,
    RejectLearnedSimilarityResponse, SetSortSimilarityRequest, SetSortSimilarityResponse,
    SortAncestorsResponse, SortChildrenResponse, SortCompareOperator, SortCompareRequest,
    SortCompareResponse, SortDescendantsResponse, SortDto, SortInfoDto, SortListResponse,
    SortParentsResponse, SortResponse, UpdateReviewStatusRequest,
};

/// Resource client for sort operations. Returned by [`ReasoningLayerClient::sorts`](crate::ReasoningLayerClient::sorts).
#[derive(Debug, Clone)]
pub struct SortsClient {
    http: HttpClient,
    tenant_id: Arc<String>,
}

impl SortsClient {
    pub(crate) fn new(http: HttpClient) -> Self {
        let tenant_id = Arc::new(http.config.tenant_id.clone());
        Self { http, tenant_id }
    }

    pub(crate) fn http(&self) -> &HttpClient {
        &self.http
    }

    /// Create a new sort.
    pub async fn create_sort(
        &self,
        request: CreateSortRequest,
        options: Option<&RequestOptions>,
    ) -> Result<SortDto, Error> {
        let response: SortResponse = self.http.post("/sorts", &request, options).await?;
        Ok(response.sort)
    }

    /// Get a sort by ID.
    pub async fn get_sort(
        &self,
        sort_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<SortDto, Error> {
        let path = format!("/sorts/{}", encode(sort_id));
        let response: SortResponse = self.http.get(&path, None, options).await?;
        Ok(response.sort)
    }

    /// Delete a sort by ID.
    pub async fn delete_sort(
        &self,
        sort_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<(), Error> {
        let path = format!("/sorts/{}", encode(sort_id));
        let _: serde_json::Value = self.http.delete(&path, None, options).await?;
        Ok(())
    }

    /// List all sorts for the authenticated tenant.
    pub async fn list_sorts(
        &self,
        options: Option<&RequestOptions>,
    ) -> Result<Vec<SortDto>, Error> {
        let path = format!("/sorts/tenant/{}", encode(self.tenant_id.as_str()));
        let response: SortListResponse = self.http.get(&path, None, options).await?;
        Ok(response.sorts)
    }

    /// Bulk-create sorts with name-based parent references.
    pub async fn bulk_create_sorts(
        &self,
        request: BulkCreateSortsRequest,
        options: Option<&RequestOptions>,
    ) -> Result<BulkCreateSortsResponse, Error> {
        self.http.post("/sorts/bulk", &request, options).await
    }

    /// Check if a sort is a subtype of another.
    pub async fn is_subtype(
        &self,
        child_id: &str,
        parent_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<bool, Error> {
        let path = format!("/sorts/{}/subtype/{}", encode(child_id), encode(parent_id));
        let response: IsSubtypeResponse = self.http.get(&path, None, options).await?;
        Ok(response.is_subtype)
    }

    /// Compute the Greatest Lower Bound (GLB) of two sorts.
    pub async fn compute_glb(
        &self,
        request: GlbRequest,
        options: Option<&RequestOptions>,
    ) -> Result<GlbResponse, Error> {
        self.http.post("/sorts/glb", &request, options).await
    }

    /// Compute the Least Upper Bound (LUB) of two sorts.
    pub async fn compute_lub(
        &self,
        request: LubRequest,
        options: Option<&RequestOptions>,
    ) -> Result<LubResponse, Error> {
        self.http.post("/sorts/lub", &request, options).await
    }

    /// Decode a GLB as a type disjunction.
    pub async fn decode_glb(
        &self,
        request: GlbRequest,
        options: Option<&RequestOptions>,
    ) -> Result<DecodeGlbResponse, Error> {
        self.http.post("/sorts/glb/decode", &request, options).await
    }

    /// Find the most specific type shared by two sorts — alias for [`SortsClient::compute_glb`].
    pub async fn find_common_subtype(
        &self,
        sort1_id: &str,
        sort2_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<GlbResponse, Error> {
        self.compute_glb(
            GlbRequest {
                sort1_id: sort1_id.to_string(),
                sort2_id: sort2_id.to_string(),
            },
            options,
        )
        .await
    }

    /// Find the most general type that covers both sorts — alias for [`SortsClient::compute_lub`].
    pub async fn find_common_supertype(
        &self,
        sort1_id: &str,
        sort2_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<LubResponse, Error> {
        self.compute_lub(
            LubRequest {
                sort1_id: sort1_id.to_string(),
                sort2_id: sort2_id.to_string(),
            },
            options,
        )
        .await
    }

    /// Explain the relationship between two sorts — alias for [`SortsClient::decode_glb`].
    pub async fn explain_common_subtype(
        &self,
        sort1_id: &str,
        sort2_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<DecodeGlbResponse, Error> {
        self.decode_glb(
            GlbRequest {
                sort1_id: sort1_id.to_string(),
                sort2_id: sort2_id.to_string(),
            },
            options,
        )
        .await
    }

    /// Get direct children of a sort.
    pub async fn get_children(
        &self,
        sort_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<Vec<SortInfoDto>, Error> {
        let path = format!("/sorts/{}/children", encode(sort_id));
        let response: SortChildrenResponse = self.http.get(&path, None, options).await?;
        Ok(response.children)
    }

    /// Get direct parents of a sort.
    pub async fn get_parents(
        &self,
        sort_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<Vec<SortInfoDto>, Error> {
        let path = format!("/sorts/{}/parents", encode(sort_id));
        let response: SortParentsResponse = self.http.get(&path, None, options).await?;
        Ok(response.parents)
    }

    /// Get all ancestors of a sort (transitive parents).
    pub async fn get_ancestors(
        &self,
        sort_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<Vec<SortInfoDto>, Error> {
        let path = format!("/sorts/{}/ancestors", encode(sort_id));
        let response: SortAncestorsResponse = self.http.get(&path, None, options).await?;
        Ok(response.ancestors)
    }

    /// Get all descendants of a sort (transitive children).
    pub async fn get_descendants(
        &self,
        sort_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<Vec<SortInfoDto>, Error> {
        let path = format!("/sorts/{}/descendants", encode(sort_id));
        let response: SortDescendantsResponse = self.http.get(&path, None, options).await?;
        Ok(response.descendants)
    }

    /// Get sorts compatible with a given sort.
    pub async fn get_compatible(
        &self,
        sort_id: &str,
        options: Option<&RequestOptions>,
    ) -> Result<Vec<SortDto>, Error> {
        let path = format!("/sorts/{}/compatible", encode(sort_id));
        let response: SortListResponse = self.http.get(&path, None, options).await?;
        Ok(response.sorts)
    }

    /// Compare two sorts using an operator. The `tenant_id` is injected from the client config.
    pub async fn compare_sorts(
        &self,
        operator: SortCompareOperator,
        sort_a: impl Into<String>,
        sort_b: impl Into<String>,
        options: Option<&RequestOptions>,
    ) -> Result<SortCompareResponse, Error> {
        let request = SortCompareRequest {
            operator,
            sort_a: sort_a.into(),
            sort_b: sort_b.into(),
            tenant_id: self.tenant_id.as_str().to_string(),
        };
        self.http.post("/sorts/compare", &request, options).await
    }

    /// Trigger re-indexing of the sort hierarchy.
    pub async fn index_sorts(&self, options: Option<&RequestOptions>) -> Result<(), Error> {
        let _: serde_json::Value = self
            .http
            .post("/sorts/index", &serde_json::json!({}), options)
            .await?;
        Ok(())
    }

    /// Update the review status of a sort.
    pub async fn update_review_status(
        &self,
        sort_id: &str,
        request: UpdateReviewStatusRequest,
        options: Option<&RequestOptions>,
    ) -> Result<(), Error> {
        let path = format!("/sorts/{}/review", encode(sort_id));
        let _: serde_json::Value = self.http.post(&path, &request, options).await?;
        Ok(())
    }

    // ─── Similarity ───────────────────────────────────────────────────────

    /// Get the direct similarity degree between two sorts.
    pub async fn get_sort_similarity(
        &self,
        request: GetSortSimilarityRequest,
        options: Option<&RequestOptions>,
    ) -> Result<GetSortSimilarityResponse, Error> {
        self.http
            .post("/sorts/similarity/get", &request, options)
            .await
    }

    /// Set similarity between two sorts (symmetric).
    pub async fn set_sort_similarity(
        &self,
        request: SetSortSimilarityRequest,
        options: Option<&RequestOptions>,
    ) -> Result<SetSortSimilarityResponse, Error> {
        self.http.post("/sorts/similarity", &request, options).await
    }

    /// Bulk-set sort similarities.
    pub async fn bulk_set_similarities(
        &self,
        request: BulkSetSimilaritiesRequest,
        options: Option<&RequestOptions>,
    ) -> Result<BulkSetSimilaritiesResponse, Error> {
        self.http
            .post("/sorts/similarity/bulk", &request, options)
            .await
    }

    /// Compute the preorder degree between two sorts.
    pub async fn get_preorder_degree(
        &self,
        request: GetPreorderDegreeRequest,
        options: Option<&RequestOptions>,
    ) -> Result<GetPreorderDegreeResponse, Error> {
        self.http
            .post("/sorts/preorder-degree", &request, options)
            .await
    }

    /// Get equivalence classes under the combined preorder.
    pub async fn get_equivalence_classes(
        &self,
        options: Option<&RequestOptions>,
    ) -> Result<GetEquivalenceClassesResponse, Error> {
        self.http
            .get("/sorts/equivalence-classes", None, options)
            .await
    }

    /// Learn sort similarities from effect features.
    pub async fn learn_similarities(
        &self,
        request: LearnSortSimilaritiesRequest,
        options: Option<&RequestOptions>,
    ) -> Result<LearnSortSimilaritiesResponse, Error> {
        self.http
            .post("/sorts/learn-similarities", &request, options)
            .await
    }

    /// Get all learned similarities.
    pub async fn get_learned_similarities(
        &self,
        options: Option<&RequestOptions>,
    ) -> Result<LearnedSimilarityListResponse, Error> {
        self.http
            .get("/sorts/learned-similarities", None, options)
            .await
    }

    /// Approve a learned similarity.
    pub async fn approve_similarity(
        &self,
        request: ApproveLearnedSimilarityRequest,
        options: Option<&RequestOptions>,
    ) -> Result<ApproveLearnedSimilarityResponse, Error> {
        self.http
            .post("/sorts/learned-similarities/approve", &request, options)
            .await
    }

    /// Reject a learned similarity.
    pub async fn reject_similarity(
        &self,
        request: RejectLearnedSimilarityRequest,
        options: Option<&RequestOptions>,
    ) -> Result<RejectLearnedSimilarityResponse, Error> {
        self.http
            .post("/sorts/learned-similarities/reject", &request, options)
            .await
    }

    /// Alias for [`SortsClient::bulk_create_sorts`].
    pub async fn create_many(
        &self,
        request: BulkCreateSortsRequest,
        options: Option<&RequestOptions>,
    ) -> Result<BulkCreateSortsResponse, Error> {
        self.bulk_create_sorts(request, options).await
    }
}

fn encode(segment: &str) -> String {
    // Only percent-encode characters that are unsafe in a path segment. UUIDs and names with
    // hyphens/underscores pass through unchanged; anything else is encoded defensively.
    url::form_urlencoded::byte_serialize(segment.as_bytes()).collect()
}