odoo-api 0.2.5

Type-safe and full-coverage implementation of the Odoo JSON-RPC API, including ORM and Web methods. Supports sessioning, multi-database, async and blocking via reqwest, and bring-your-own requests.
Documentation
use serde::{Serialize, Deserialize};
use serde_tuple::{Deserialize_tuple, Serialize_tuple};
use serde_json::Value;

/// An Odoo domain
/// 
/// Domains are used in search functions (e.g., `search` and `search_read`), and
/// are eventually rendered out into an SQL `WHERE` statement. The domain consists of:
///  * **Terms** are a 3-tuple of field name, comparison operator, and value. For example,
///    we might have `['is_active', '=', true]`, which would generate the SQL `is_active = TRUE`.
///    More complex comparisons are possible (text matching, parent/child relationships,
///    and Many2one relation traversal)
///  * **Operators** are used to chain terms together (e.g., `AND` or `OR`)
/// 
/// The "implict" operator, when one isn't specified, is `AND`.
/// 
/// # Usage
/// ```no_run
/// use odoo_api::client::domain::{Domain, DomainTermOperator as OP};
/// 
/// let domain = Domain::new()
///     .add("is_active", OP::Eq, true)
///     .and()
/// 
/// ```
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Domain(Vec<DomainLeaf>);


/// A domain "leaf"
/// 
/// The term "leaf" is a bit of a misnomer (perhaps the Domain is a vine or crawler).
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DomainLeaf {
    Term(DomainTerm),
    Operator(DomainOperator)
}

#[derive(Debug, Serialize_tuple, Deserialize_tuple)]
pub struct DomainTerm {
    /// a field name of the current model, or a relationship traversal through a `Many2one` using dot-notation e.g. `'street'` or `'partner_id.country'`
    field_name: String,

    /// an operator used to compare the `field_name` with the `value`
    operator: DomainTermOperator,

    /// variable type, must be comparable (through `operator`) to the named field.
    value: Value,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DomainTermOperator {
    /// equals to
    #[serde(rename = "=")]
    Eq,

    /// not equals to
    #[serde(rename = "!=")]
    Ne,

    /// greater than
    #[serde(rename = ">")]
    Gt,

    /// greater than or equal to
    #[serde(rename = ">=")]
    Ge,

    /// less than
    #[serde(rename = "<")]
    Lt,

    /// less than or equal to
    #[serde(rename = "<=")]
    Le,

    /// unset or equals to (returns true if value is either `None` or `False`, otherwise behaves like `=`)
    #[serde(rename = "=?")]
    EqOrUnset,

    /// matches `field_name` against the `%value% `pattern. Similar to `=like` but wraps value with `%` before matching
    #[serde(rename = "like")]
    Like,

    /// matches `field_name` against the `value` pattern. An underscore `_` in the pattern stands for (matches) any single character; a percent sign `%` matches any string of zero or more characters.
    #[serde(rename = "=like")]
    LikeEq,

    /// doesn’t match against the `%value%` pattern
    #[serde(rename = "not like")]
    NotLike,

    /// case insensitive `like`
    #[serde(rename = "ilike")]
    ILike,

    /// case insensitive `=like`
    #[serde(rename = "=ilike")]
    ILikeEq,

    /// case insensitive `not like`
    #[serde(rename = "not ilike")]
    NotILike,

    /// is equal to any of the items from `value`, `value` should be a list of items
    #[serde(rename = "in")]
    In,

    /// is unequal to all of the items from `value`
    #[serde(rename = "not in")]
    NotIn,

    /// is a child (descendant) of a `value` record (value can be either one item or a list of items).
    /// 
    /// Takes the semantics of the model into account (i.e following the relationship field named by `_parent_name`).
    #[serde(rename = "child_of")]
    ChildOf,

    /// is a parent (ascendant) of a `value` record (value can be either one item or a list of items).
    ///
    /// Takes the semantics of the model into account (i.e following the relationship field named by `_parent_name`).
    #[serde(rename = "parent_of")]
    ParentOf,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum DomainOperator {
    /// logical *NOT*, arity 1.
    #[serde(rename = "!")]
    Not,

    /// logical *AND*, default operation to combine criteria following one another. Arity 2 (uses the next 2 criteria or combinations).
    #[serde(rename = "&")]
    And,

    /// logical *OR*, arity 2.
    #[serde(rename = "|")]
    Or,
}

impl Domain {
    pub fn new() -> Self {
        Self(Vec::new())
    }
    pub fn push(&mut self, value: DomainLeaf) {
        self.0.push(value)
    }
    pub fn insert(&mut self, index: usize, item: DomainLeaf) {
        self.0.insert(index, item)
    }

    pub fn add(&mut self, field_name: &str, operator: DomainTermOperator, value: impl Into<Value>) -> &mut Self {
        self.insert(0, DomainLeaf::new(field_name, operator, value));
        self
    }
    pub fn and(&mut self, field_name: &str, operator: DomainTermOperator, value: impl Into<Value>) -> &mut Self {
        self.insert(0, DomainLeaf::AND());
        self.insert(0, DomainLeaf::new(field_name, operator, value));
        self
    }
    pub fn or(&mut self, field_name: &str, operator: DomainTermOperator, value: impl Into<Value>) -> &mut Self {
        self.insert(0, DomainLeaf::AND());
        self.insert(0, DomainLeaf::new(field_name, operator, value));

        self
    }

    pub fn normalize(self) -> Self {
        self
    }

}

impl DomainLeaf {
    pub fn new(field_name: &str, operator: DomainTermOperator, value: impl Into<Value>) -> Self {
        Self::Term(DomainTerm {
            field_name: field_name.into(),
            operator,
            value: value.into()
        })
    }

    #[allow(non_snake_case)]
    pub fn AND() -> Self {
        Self::Operator(DomainOperator::And)
    }
    #[allow(non_snake_case)]
    pub fn OR() -> Self {
        Self::Operator(DomainOperator::Or)
    }
    #[allow(non_snake_case)]
    pub fn NOT() -> Self {
        Self::Operator(DomainOperator::Not)
    }
}

pub mod test {}