odoo_api/service/orm.rs
1//! The Odoo "ORM" pseudo-service
2//!
3//! This isn't really an Odoo "service", but instead is another layer of abstraction
4//! over the object `execute` and `execute_kw` methods, providing a nicer interface
5//! with better type checking.
6
7use crate as odoo_api;
8use crate::jsonrpc::{OdooId, OdooIds, OdooOrmMethod};
9use odoo_api_macros::odoo_orm;
10use serde::ser::SerializeTuple;
11use serde::{de, Deserialize, Deserializer, Serialize};
12use serde_json::{Map, Value};
13use serde_tuple::{Deserialize_tuple, Serialize_tuple};
14use std::collections::HashMap;
15use std::fmt;
16
17/// Create a new record (or set of records)
18///
19/// ## Example
20/// ```no_run
21/// # #[cfg(not(feature = "types-only"))]
22/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
23/// # use odoo_api::{OdooClient, jvec, jmap};
24/// # let client = OdooClient::new_reqwest_blocking("")?;
25/// # let mut client = client.authenticate_manual("", "", 1, "", None);
26/// // create a single record
27/// let resp = client.create(
28/// "res.partner",
29/// jmap!{
30/// "name": "Example Partner",
31/// "email": "hello@example.com"
32/// }
33/// ).send()?;
34///
35/// // create multiple records
36/// let resp2 = client.create(
37/// "res.partner",
38/// jvec![
39/// {"name": "Partner #1", "email": "marco@example.com"},
40/// {"name": "Partner #2", "email": "polo@example.com"}
41/// ]
42/// ).send()?;
43///
44/// println!("New partner ids: {:?}", resp2.ids);
45/// # Ok(())
46/// # }
47/// ```
48///<br />
49///
50/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L3829-L3964)
51#[odoo_orm(
52 method = "create",
53 args = ["values"],
54 kwargs = [],
55)]
56#[derive(Debug)]
57pub struct Create {
58 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
59 pub database: String,
60
61 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
62 pub uid: OdooId,
63
64 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
65 pub password: String,
66
67 /// The Odoo model
68 pub model: String,
69
70 /// Values for the new record(s)
71 pub values: CreateVals,
72}
73
74/// The values to a [`Create`] request
75///
76/// The Odoo `create()` function accepts either a dictionary (create one record),
77/// or a list of dictionaries (create multiple records). To support those in an
78/// ergonomic way, we will accept an enum for the value.
79///
80/// This enum implements `From<...>` for both one & multi requests:
81/// ```ignore
82/// // create a single record
83/// client.create(
84/// "res.users",
85/// jmap!{
86/// "name": "Hello, world!",
87/// }
88/// );
89///
90/// // create multiple records
91/// client.create(
92/// "res.users",
93/// jvec![
94/// {"name": "Partner #1"},
95/// {"name": "Partner #2"}
96/// ]
97/// );
98/// ```
99#[derive(Debug, Serialize)]
100#[serde(untagged)]
101pub enum CreateVals {
102 /// Create a single new record
103 One(Map<String, Value>),
104
105 /// Create multiple new records
106 Multi(Vec<Value>),
107}
108
109impl From<Map<String, Value>> for CreateVals {
110 fn from(value: Map<String, Value>) -> Self {
111 Self::One(value)
112 }
113}
114
115impl From<Vec<Value>> for CreateVals {
116 fn from(value: Vec<Value>) -> Self {
117 Self::Multi(value)
118 }
119}
120
121/// The response to a [`Create`] requests
122#[derive(Debug, Serialize, Deserialize)]
123#[serde(transparent)]
124pub struct CreateResponse {
125 /// The new record(s) id(s)
126 pub ids: CreateResponseItem,
127}
128
129/// Container for the [`CreateResponse`] items
130///
131/// Because thr [`Create`] request can create one OR multiple records, the response
132/// may be one or multiple ids. In the "one" case, Odoo returns a plain int. In
133/// the "multi" case, Odoo returns an array of ints.
134#[derive(Debug, Serialize, Deserialize)]
135#[serde(untagged)]
136pub enum CreateResponseItem {
137 /// The new records' id
138 One(OdooId),
139
140 /// A list of the ids for the new records
141 Multi(Vec<OdooId>),
142}
143
144/// Read data from a record (or set of records)
145///
146/// ## Example
147/// ```no_run
148/// # #[cfg(not(feature = "types-only"))]
149/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
150/// # use odoo_api::{OdooClient, svec};
151/// # let client = OdooClient::new_reqwest_blocking("")?;
152/// # let mut client = client.authenticate_manual("", "", 1, "", None);
153/// // read from a single record
154/// let resp = client.read(
155/// "res.partner",
156/// 1,
157/// svec!["id", "login"]
158/// ).send()?;
159///
160/// // read from multiple records
161/// let resp = client.read(
162/// "res.partner",
163/// vec![1, 2, 3],
164/// svec!["id", "login"]
165/// ).send()?;
166///
167/// println!("Data: {:?}", resp.data);
168/// # Ok(())
169/// # }
170/// ```
171///<br />
172///
173/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L2958-L2991)
174#[odoo_orm(
175 method = "read",
176 args = ["ids"],
177 kwargs = ["fields"],
178)]
179#[derive(Debug)]
180pub struct Read {
181 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
182 pub database: String,
183
184 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
185 pub uid: OdooId,
186
187 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
188 pub password: String,
189
190 /// The Odoo model
191 pub model: String,
192
193 /// The records
194 pub ids: OdooIds,
195
196 /// The fields to be fetched
197 pub fields: Vec<String>,
198}
199
200/// The response to a [`Read`] request
201#[derive(Debug, Serialize, Deserialize)]
202#[serde(transparent)]
203pub struct ReadResponse {
204 /// The fetched fields
205 pub data: Vec<Map<String, Value>>,
206}
207
208/// Write data to a record (or set of records)
209///
210/// ## Example
211/// ```no_run
212/// # #[cfg(not(feature = "types-only"))]
213/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
214/// # use odoo_api::{OdooClient, jmap};
215/// # let client = OdooClient::new_reqwest_blocking("")?;
216/// # let mut client = client.authenticate_manual("", "", 1, "", None);
217/// // write to a single record
218/// client.write(
219/// "res.partner",
220/// 1,
221/// jmap!{
222/// "name": "New Partner Name"
223/// }
224/// ).send()?;
225///
226/// // write to multiple records
227/// client.write(
228/// "res.partner",
229/// vec![1, 2, 3],
230/// jmap!{
231/// "website": "https://www.example.com"
232/// }
233/// ).send()?;
234/// # Ok(())
235/// # }
236/// ```
237///<br />
238///
239/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L3585-L3775)
240#[odoo_orm(
241 method = "write",
242 args = ["ids", "values"],
243 kwargs = [],
244)]
245#[derive(Debug)]
246pub struct Write {
247 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
248 pub database: String,
249
250 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
251 pub uid: OdooId,
252
253 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
254 pub password: String,
255
256 /// The Odoo model
257 pub model: String,
258
259 /// The records
260 pub ids: OdooIds,
261
262 /// The values to write
263 pub values: Map<String, Value>,
264}
265
266/// The response to a [`Write`] request
267#[derive(Debug, Serialize, Deserialize)]
268#[serde(transparent)]
269pub struct WriteResponse {
270 pub ok: bool,
271}
272
273/// Delete a record (or set of records)
274///
275/// ## Example
276/// ```no_run
277/// # #[cfg(not(feature = "types-only"))]
278/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
279/// # use odoo_api::{OdooClient, jmap};
280/// # let client = OdooClient::new_reqwest_blocking("")?;
281/// # let mut client = client.authenticate_manual("", "", 1, "", None);
282/// // delete one record
283/// client.unlink(
284/// "res.partner",
285/// 1
286/// ).send()?;
287///
288/// // delete multiple records
289/// client.unlink(
290/// "res.partner",
291/// vec![1, 2, 3]
292/// ).send()?;
293/// # Ok(())
294/// # }
295/// ```
296///<br />
297///
298/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L3488-L3583)
299#[odoo_orm(
300 method = "unlink",
301 args = ["ids"],
302 kwargs = [],
303)]
304#[derive(Debug)]
305pub struct Unlink {
306 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
307 pub database: String,
308
309 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
310 pub uid: OdooId,
311
312 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
313 pub password: String,
314
315 /// The Odoo model
316 pub model: String,
317
318 /// The records to be deleted
319 pub ids: OdooIds,
320}
321
322#[derive(Debug, Serialize, Deserialize)]
323#[serde(transparent)]
324pub struct UnlinkResponse {
325 pub ok: bool,
326}
327
328/// Read some grouped data from a record (or set of records)
329///
330/// ## Example
331/// ```no_run
332/// # #[cfg(not(feature = "types-only"))]
333/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
334/// # use odoo_api::{OdooClient, jvec, svec};
335/// # let client = OdooClient::new_reqwest_blocking("")?;
336/// # let mut client = client.authenticate_manual("", "", 1, "", None);
337/// client.read_group(
338/// "res.partner",
339///
340/// // domain
341/// jvec![
342/// ["email", "=ilike", "%@example.com"],
343/// ["phone", "!=", false]
344/// ],
345///
346/// // fields
347/// svec!["id", "name", "email", "phone"],
348///
349/// // groupby
350/// svec!["create_date:month", "email"],
351///
352/// Some(0), // offset
353/// Some(10), // limit
354/// Some("create_date desc".into()), // orderby
355/// false // lazy
356/// ).send()?;
357///
358/// # Ok(())
359/// # }
360/// ```
361///<br />
362///
363/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L2178-L2236)
364#[odoo_orm(
365 method = "read_group",
366 args = ["domain", "fields", "groupby"],
367 kwargs = ["offset", "limit", "orderby", "lazy"],
368)]
369#[derive(Debug)]
370pub struct ReadGroup {
371 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
372 pub database: String,
373
374 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
375 pub uid: OdooId,
376
377 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
378 pub password: String,
379
380 /// The Odoo model
381 pub model: String,
382
383 /// The domain to search on
384 pub domain: Vec<Value>,
385
386 /// The fields to read
387 pub fields: Vec<String>,
388
389 /// The groupby descriptions
390 ///
391 /// See [`read_group`](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L2191-L2195) for more information.
392 pub groupby: Vec<String>,
393
394 /// An optional offset, for paging
395 pub offset: Option<u32>,
396
397 /// An optional limit
398 pub limit: Option<u32>,
399
400 /// An optional ordering description
401 ///
402 /// This corresponds roughly with PostgreSQL's `ORDER BY` clause, for example:
403 /// `create_date desc, id asc, active asc`
404 pub orderby: Option<String>,
405
406 /// Enable lazy-grouping
407 ///
408 /// If `true`, then only the first `groupby` fragment is evaluated, and all
409 /// other fragments are added to the `__context` key in the response.
410 ///
411 /// This may be useful when initially showing a list of top-level groups; evaluating
412 /// only the first `groupby` will be much faster for large datasets.
413 pub lazy: bool,
414}
415
416//TODO: a better response type (e.g., struct with __domain/etc, and a `data` key for the actual fields)
417/// The response to a [`ReadGroup`] request
418#[derive(Debug, Serialize, Deserialize)]
419#[serde(transparent)]
420pub struct ReadGroupResponse {
421 pub result: Vec<Map<String, Value>>,
422}
423
424/// Perform a `search` and `read` in one call
425///
426/// ## Example
427/// ```no_run
428/// # #[cfg(not(feature = "types-only"))]
429/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
430/// # use odoo_api::{OdooClient, jvec, svec};
431/// # let client = OdooClient::new_reqwest_blocking("")?;
432/// # let mut client = client.authenticate_manual("", "", 1, "", None);
433/// client.search_read(
434/// "res.partner",
435///
436/// // domain
437/// jvec![
438/// ["email", "=ilike", "%@example.com"],
439/// ["phone", "!=", false]
440/// ],
441///
442/// // fields
443/// svec!["id", "name", "email", "phone"],
444///
445/// Some(0), // offset
446/// Some(10), // limit
447/// Some("create_date desc".into()), // order
448/// ).send()?;
449///
450/// # Ok(())
451/// # }
452/// ```
453///<br />
454///
455/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L4920-L4963)
456#[odoo_orm(
457 method = "search_read",
458 args = [],
459 kwargs = ["domain", "fields", "offset", "limit", "order"],
460)]
461#[derive(Debug)]
462pub struct SearchRead {
463 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
464 pub database: String,
465
466 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
467 pub uid: OdooId,
468
469 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
470 pub password: String,
471
472 /// The Odoo model
473 pub model: String,
474
475 /// The domain to search on
476 pub domain: Vec<Value>,
477
478 /// The fields to read
479 pub fields: Vec<String>,
480
481 /// An optional offset, for paging
482 pub offset: Option<u32>,
483
484 /// An optional limit
485 pub limit: Option<u32>,
486
487 /// An optional ordering description
488 ///
489 /// This corresponds roughly with PostgreSQL's `ORDER BY` clause, for example:
490 /// `create_date desc, id asc, active asc`
491 pub order: Option<String>,
492}
493
494#[derive(Debug, Serialize, Deserialize)]
495#[serde(transparent)]
496pub struct SearchReadResponse {
497 pub data: Vec<Map<String, Value>>,
498}
499
500//TODO: notes about the `count` flag (maybe disable that - we have search_count)
501/// Return the ids of records matching a domain
502///
503/// ## Example
504/// ```no_run
505/// # #[cfg(not(feature = "types-only"))]
506/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
507/// # use odoo_api::{OdooClient, jvec};
508/// # let client = OdooClient::new_reqwest_blocking("")?;
509/// # let mut client = client.authenticate_manual("", "", 1, "", None);
510/// client.search(
511/// "res.partner",
512///
513/// // domain
514/// jvec![
515/// ["email", "=ilike", "%@example.com"],
516/// ["phone", "!=", false]
517/// ],
518///
519/// Some(0), // offset
520/// Some(10), // limit
521/// Some("create_date desc".into()), // order
522/// ).send()?;
523///
524/// # Ok(())
525/// # }
526/// ```
527///<br />
528///
529/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L1517-L1537)
530#[odoo_orm(
531 method = "search",
532 args = ["domain"],
533 kwargs = ["offset", "limit", "order"],
534)]
535#[derive(Debug)]
536pub struct Search {
537 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
538 pub database: String,
539
540 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
541 pub uid: OdooId,
542
543 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
544 pub password: String,
545
546 /// The Odoo model
547 pub model: String,
548
549 /// The domain to search on
550 pub domain: Vec<Value>,
551
552 /// An optional offset, for paging
553 pub offset: Option<u32>,
554
555 /// An optional limit
556 pub limit: Option<u32>,
557
558 /// An optional ordering description
559 ///
560 /// This corresponds roughly with PostgreSQL's `ORDER BY` clause, for example:
561 /// `create_date desc, id asc, active asc`
562 pub order: Option<String>,
563}
564
565/// The response to a [`Search`] request
566#[derive(Debug, Serialize, Deserialize)]
567#[serde(transparent)]
568pub struct SearchResponse {
569 pub records: Vec<OdooId>,
570}
571
572/// Return the count of records matching a domain
573///
574/// ## Example
575/// ```no_run
576/// # #[cfg(not(feature = "types-only"))]
577/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
578/// # use odoo_api::{OdooClient, jvec};
579/// # let client = OdooClient::new_reqwest_blocking("")?;
580/// # let mut client = client.authenticate_manual("", "", 1, "", None);
581/// client.search_count(
582/// "res.partner",
583///
584/// // domain
585/// jvec![
586/// ["email", "=ilike", "%@example.com"],
587/// ["phone", "!=", false]
588/// ],
589///
590/// None, // limit
591/// ).send()?;
592/// # Ok(())
593/// # }
594/// ```
595///<br />
596///
597/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L1503-L1515)
598#[odoo_orm(
599 method = "search_count",
600 args = ["domain"],
601 kwargs = ["limit"],
602)]
603#[derive(Debug)]
604pub struct SearchCount {
605 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
606 pub database: String,
607
608 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
609 pub uid: OdooId,
610
611 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
612 pub password: String,
613
614 /// The Odoo model
615 pub model: String,
616
617 /// The domain to search on
618 pub domain: Vec<Value>,
619
620 /// An optional limit
621 pub limit: Option<u32>,
622}
623
624/// The response to a [`SearchCount`] request
625#[derive(Debug, Serialize, Deserialize)]
626#[serde(transparent)]
627pub struct SearchCountResponse {
628 pub count: u32,
629}
630
631/// Copy a record
632///
633/// ## Example
634/// ```no_run
635/// # #[cfg(not(feature = "types-only"))]
636/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
637/// # use odoo_api::{OdooClient, jmap};
638/// # let client = OdooClient::new_reqwest_blocking("")?;
639/// # let mut client = client.authenticate_manual("", "", 1, "", None);
640/// client.copy(
641/// "res.partner",
642///
643/// 2, // record id
644/// None, // override fields
645/// ).send()?;
646/// # Ok(())
647/// # }
648/// ```
649///<br />
650///
651/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L4755-L4771)
652#[odoo_orm(
653 method = "copy",
654 args = ["id"],
655 kwargs = ["default"],
656)]
657#[derive(Debug)]
658pub struct Copy {
659 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
660 pub database: String,
661
662 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
663 pub uid: OdooId,
664
665 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
666 pub password: String,
667
668 /// The Odoo model
669 pub model: String,
670
671 /// The record to copy
672 pub id: OdooId,
673
674 /// The fields to be overridden
675 pub default: Option<Map<String, Value>>,
676}
677
678#[derive(Debug, Serialize, Deserialize)]
679#[serde(transparent)]
680pub struct CopyResponse {
681 pub id: OdooId,
682}
683
684/// Check if the record(s) exist in the Odoo database
685///
686/// **Note**: This method works by accepting a list of ids, and returning only
687/// the ids that actually exist. See [`ExistsResponse`] for more info.
688///
689/// ## Example
690/// ```no_run
691/// # #[cfg(not(feature = "types-only"))]
692/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
693/// # use odoo_api::{OdooClient, jmap};
694/// # let client = OdooClient::new_reqwest_blocking("")?;
695/// # let mut client = client.authenticate_manual("", "", 1, "", None);
696/// client.exists(
697/// "res.partner",
698/// vec![1, 2, -1, 999999999]
699/// ).send()?;
700///
701/// // The response would be [1, 2], assuming that -1 and 999999999 do not exist
702/// # Ok(())
703/// # }
704/// ```
705///<br />
706///
707/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L4773-L4793)
708#[odoo_orm(
709 method = "exists",
710 args = ["ids"],
711 kwargs = [],
712)]
713#[derive(Debug)]
714pub struct Exists {
715 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
716 pub database: String,
717
718 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
719 pub uid: OdooId,
720
721 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
722 pub password: String,
723
724 /// The Odoo model
725 pub model: String,
726
727 /// The ids to check
728 pub ids: OdooIds,
729}
730
731/// The response to an [`Exists`] call
732///
733/// The [`Exists`] method is soemwhat unituitive; It accepts *multiple* ids, then
734/// returns only the ids that actually exist (rather than returning true/false).
735///
736/// To use this method, you should pass the record ids you want to check, then
737/// test whether those ids were returned in the `existing_records` field.
738#[derive(Debug, Serialize, Deserialize)]
739#[serde(transparent)]
740pub struct ExistsResponse {
741 pub existing_records: OdooIds,
742}
743
744/// An access operation type
745#[derive(Debug, Serialize)]
746pub enum AccessOperation {
747 #[serde(rename = "create")]
748 Create,
749
750 #[serde(rename = "read")]
751 Read,
752
753 #[serde(rename = "write")]
754 Write,
755
756 #[serde(rename = "unlink")]
757 Unlink,
758}
759
760/// Check model access rights (according to `ir.model.access`)
761///
762/// This method checks against `ir.model.access`, e.g. basic per-group CRUD rules.
763/// You should also call [`CheckAccessRules`] in order to determine if any advanced
764/// access rules apply to this model/user.
765///
766/// **Note**: This method raises an API exception if the access rights check fails.
767/// You probably want to specify `raise_exception: false`, which will cause the
768/// request to return `false` when the check fails.
769///
770/// ## Example
771/// ```no_run
772/// # #[cfg(not(feature = "types-only"))]
773/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
774/// # use odoo_api::{OdooClient, jmap};
775/// # let client = OdooClient::new_reqwest_blocking("")?;
776/// # let mut client = client.authenticate_manual("", "", 1, "", None);
777/// use odoo_api::service::orm::AccessOperation;
778/// client.check_access_rights(
779/// "stock.quant",
780/// AccessOperation::Unlink,
781/// false // raise_exception
782/// ).send()?;
783///
784/// // Quants are never deleteable, so this will return `false`
785/// # Ok(())
786/// # }
787/// ```
788///<br />
789///
790/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L3407-L3417)
791#[odoo_orm(
792 method = "check_access_rights",
793 args = ["operation"],
794 kwargs = ["raise_exception"],
795)]
796#[derive(Debug)]
797pub struct CheckAccessRights {
798 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
799 pub database: String,
800
801 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
802 pub uid: OdooId,
803
804 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
805 pub password: String,
806
807 /// The Odoo model
808 pub model: String,
809
810 /// The CRUD operation to check
811 pub operation: AccessOperation,
812
813 /// How should check failures be reported?
814 ///
815 /// * `true`: An API exception is raised (catchable with `?`, etc)
816 /// * `false`: The [`CheckAccessRightsResponse`] `ok` field will be set to `false`
817 pub raise_exception: bool,
818}
819
820/// Response to a [`CheckAccessRights`] request
821#[derive(Debug, Serialize, Deserialize)]
822#[serde(transparent)]
823pub struct CheckAccessRightsResponse {
824 pub ok: bool,
825}
826
827/// Check model access rules (according to `ir.rule`)
828///
829/// This method checks against `ir.rule`, e.g. advanced domain-based CRUD rules.
830/// You should also call [`CheckAccessRights`] in order to determine if any
831/// basic CRUD/group rights apply to this model/user.
832///
833/// **NOTE**: If the access check fails, an API error will be returned. To determine
834/// if the rules passed, check for the "Success" enum variant on the response.
835///
836/// **WARNING**: This method currently raises an API exception on success. This issue
837/// will be fixed in a future release. For now, you may check for
838///
839/// ## Example
840/// ```no_run
841/// # #[cfg(not(feature = "types-only"))]
842/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
843/// # use odoo_api::{OdooClient, jmap};
844/// # let client = OdooClient::new_reqwest_blocking("")?;
845/// # let mut client = client.authenticate_manual("", "", 1, "", None);
846/// use odoo_api::service::orm::AccessOperation;
847/// client.check_access_rules(
848/// "res.partner",
849/// vec![1, 2], // records
850/// AccessOperation::Unlink,
851/// ).send()?;
852/// # Ok(())
853/// # }
854/// ```
855///<br />
856///
857/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L3419-L3453)
858#[odoo_orm(
859 method = "check_access_rule",
860 name = "check_access_rules",
861 args = ["ids", "operation"],
862 kwargs = [],
863)]
864#[derive(Debug)]
865pub struct CheckAccessRules {
866 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
867 pub database: String,
868
869 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
870 pub uid: OdooId,
871
872 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
873 pub password: String,
874
875 /// The Odoo model
876 pub model: String,
877
878 /// The records to be checked
879 pub ids: OdooIds,
880
881 /// The CRUD operation to check
882 pub operation: AccessOperation,
883}
884
885#[derive(Debug, Serialize, Deserialize)]
886pub struct CheckAccessRulesResponse {}
887
888/// Check the user access rights on the given fields
889///
890/// **Note**: Like the [`Exists`] method, this method accepts a list of fields,
891/// then returns the subset of those fields that can be accessed via `operation`.
892///
893/// **Note2**: This method doesn't check if the passed fields actually exist.
894///
895/// ## Example
896/// ```no_run
897/// # #[cfg(not(feature = "types-only"))]
898/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
899/// # use odoo_api::{OdooClient, jmap, svec};
900/// # let client = OdooClient::new_reqwest_blocking("")?;
901/// # let mut client = client.authenticate_manual("", "", 1, "", None);
902/// use odoo_api::service::orm::AccessOperation;
903/// client.check_field_access_rights(
904/// "res.partner",
905/// AccessOperation::Unlink,
906/// svec!["parent_id", "email", "id"]
907/// ).send()?;
908/// # Ok(())
909/// # }
910/// ```
911///<br />
912///
913/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L2864-L2956)
914#[odoo_orm(
915 method = "check_field_access_rights",
916 args = ["operation", "fields"],
917 kwargs = [],
918)]
919#[derive(Debug)]
920pub struct CheckFieldAccessRights {
921 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
922 pub database: String,
923
924 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
925 pub uid: OdooId,
926
927 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
928 pub password: String,
929
930 /// The Odoo model
931 pub model: String,
932
933 /// The CRUD operation to check
934 pub operation: AccessOperation,
935
936 /// A list of fields to check
937 pub fields: Vec<String>,
938}
939
940/// The response to a [`CheckFieldAccessRights`] request
941#[derive(Debug, Serialize, Deserialize)]
942#[serde(transparent)]
943pub struct CheckFieldAccessRightsResponse {
944 pub result: Option<Vec<String>>,
945}
946
947/// Return some metadata about the given record(s)
948///
949/// ## Example
950/// ```no_run
951/// # #[cfg(not(feature = "types-only"))]
952/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
953/// # use odoo_api::{OdooClient, jvec};
954/// # let client = OdooClient::new_reqwest_blocking("")?;
955/// # let mut client = client.authenticate_manual("", "", 1, "", None);
956/// client.get_metadata(
957/// "res.partner",
958/// vec![1, 2]
959/// ).send()?;
960/// # Ok(())
961/// # }
962/// ```
963///<br />
964///
965/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L3275-L3315)
966#[odoo_orm(
967 method = "get_metadata",
968 args = ["ids"],
969 kwargs = [],
970)]
971#[derive(Debug)]
972pub struct GetMetadata {
973 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
974 pub database: String,
975
976 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
977 pub uid: OdooId,
978
979 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
980 pub password: String,
981
982 /// The Odoo model
983 pub model: String,
984
985 /// The records to fetch metadata for
986 pub ids: OdooIds,
987}
988
989/// The response to a [`GetMetadata`] request
990#[derive(Debug, Serialize, Deserialize)]
991#[serde(transparent)]
992pub struct GetMetadataResponse {
993 pub metadata: Vec<Map<String, Value>>,
994}
995
996// Allow the map of {str: str} to be deserialized into {i32: str}
997fn get_external_id_deserialize<'de, D>(de: D) -> Result<HashMap<OdooId, String>, D::Error>
998where
999 D: Deserializer<'de>,
1000{
1001 struct Visitor;
1002 impl<'de> de::Visitor<'de> for Visitor {
1003 type Value = HashMap<OdooId, String>;
1004
1005 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1006 formatter.write_str("a map of \"id\": \"external_id\"")
1007 }
1008
1009 fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
1010 where
1011 M: de::MapAccess<'de>,
1012 {
1013 let mut map = HashMap::new();
1014
1015 // While there are entries remaining in the input, add them
1016 // into our map.
1017 while let Some((key, value)) = access.next_entry::<String, String>()? {
1018 let key = key.parse().map_err(|_e| {
1019 de::Error::invalid_value(
1020 de::Unexpected::Str(&key),
1021 &"A String representing an i32",
1022 )
1023 })?;
1024 map.insert(key, value);
1025 }
1026
1027 Ok(map)
1028 }
1029 }
1030
1031 de.deserialize_map(Visitor)
1032}
1033
1034/// Fetch the XMLID for the given record(s)
1035///
1036/// ## Example
1037/// ```no_run
1038/// # #[cfg(not(feature = "types-only"))]
1039/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
1040/// # use odoo_api::{OdooClient, jvec};
1041/// # let client = OdooClient::new_reqwest_blocking("")?;
1042/// # let mut client = client.authenticate_manual("", "", 1, "", None);
1043/// client.get_external_id(
1044/// "res.partner",
1045/// vec![1, 2]
1046/// ).send()?;
1047/// # Ok(())
1048/// # }
1049/// ```
1050///<br />
1051///
1052/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L4882-L4901)
1053#[odoo_orm(
1054 method = "get_external_id",
1055 args = ["ids"],
1056 kwargs = [],
1057)]
1058#[derive(Debug)]
1059pub struct GetExternalId {
1060 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
1061 pub database: String,
1062
1063 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
1064 pub uid: OdooId,
1065
1066 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
1067 pub password: String,
1068
1069 /// The Odoo model
1070 pub model: String,
1071
1072 /// The records to fetch external ids for
1073 pub ids: OdooIds,
1074}
1075
1076/// The response to a [`GetExternalId`] request
1077#[derive(Debug, Serialize, Deserialize)]
1078#[serde(transparent)]
1079pub struct GetExternalIdResponse {
1080 #[serde(deserialize_with = "get_external_id_deserialize")]
1081 pub external_ids: HashMap<OdooId, String>,
1082}
1083
1084/// Fetch the XMLID for the given record(s)
1085///
1086/// ## Example
1087/// ```no_run
1088/// # #[cfg(not(feature = "types-only"))]
1089/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
1090/// # use odoo_api::{OdooClient};
1091/// # let client = OdooClient::new_reqwest_blocking("")?;
1092/// # let mut client = client.authenticate_manual("", "", 1, "", None);
1093/// client.get_xml_id(
1094/// "res.partner",
1095/// vec![1, 2]
1096/// ).send()?;
1097/// # Ok(())
1098/// # }
1099/// ```
1100///<br />
1101///
1102/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L4903-L4908)
1103#[odoo_orm(
1104 method = "get_xml_id",
1105 args = ["ids"],
1106 kwargs = [],
1107)]
1108#[derive(Debug)]
1109pub struct GetXmlId {
1110 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
1111 pub database: String,
1112
1113 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
1114 pub uid: OdooId,
1115
1116 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
1117 pub password: String,
1118
1119 /// The Odoo model
1120 pub model: String,
1121
1122 /// The records to fetch XMLIDs for
1123 pub ids: OdooIds,
1124}
1125
1126/// The response to a [`GetXmlId`] request
1127#[derive(Debug, Serialize, Deserialize)]
1128#[serde(transparent)]
1129pub struct GetXmlIdResponse {
1130 #[serde(deserialize_with = "get_external_id_deserialize")]
1131 pub external_ids: HashMap<OdooId, String>,
1132}
1133
1134/// Fetch the `display_naame` for the given record(s)
1135///
1136/// ## Example
1137/// ```no_run
1138/// # #[cfg(not(feature = "types-only"))]
1139/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
1140/// # use odoo_api::{OdooClient, jmap};
1141/// # let client = OdooClient::new_reqwest_blocking("")?;
1142/// # let mut client = client.authenticate_manual("", "", 1, "", None);
1143/// client.name_get(
1144/// "res.partner",
1145/// vec![1, 2, 3]
1146/// ).send()?;
1147/// # Ok(())
1148/// # }
1149/// ```
1150/// <br />
1151///
1152/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L1560-L1584)
1153#[odoo_orm(
1154 method = "name_get",
1155 args = ["ids"],
1156 kwargs = [],
1157)]
1158#[derive(Debug)]
1159pub struct NameGet {
1160 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
1161 pub database: String,
1162
1163 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
1164 pub uid: OdooId,
1165
1166 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
1167 pub password: String,
1168
1169 /// The Odoo model
1170 pub model: String,
1171
1172 /// The domain
1173 pub ids: OdooIds,
1174}
1175
1176/// The response to a [`NameGet`] request
1177#[derive(Debug, Serialize, Deserialize)]
1178#[serde(transparent)]
1179pub struct NameGetResponse {
1180 pub display_names: Vec<NameGetResponseItem>,
1181}
1182
1183/// An individual [`NameGet`] response item
1184#[derive(Debug, Serialize_tuple, Deserialize_tuple)]
1185pub struct NameGetResponseItem {
1186 /// The record id
1187 pub id: OdooId,
1188
1189 /// The record `display_name`
1190 pub name: String,
1191}
1192
1193/// Create a new record, passing only the `name` field
1194///
1195/// ## Example
1196/// ```no_run
1197/// # #[cfg(not(feature = "types-only"))]
1198/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
1199/// # use odoo_api::{OdooClient, jmap};
1200/// # let client = OdooClient::new_reqwest_blocking("")?;
1201/// # let mut client = client.authenticate_manual("", "", 1, "", None);
1202/// client.name_create(
1203/// "res.partner",
1204/// "I am a test!".into()
1205/// ).send()?;
1206/// # Ok(())
1207/// # }
1208/// ```
1209/// <br />
1210///
1211/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L1586-L1606)
1212#[odoo_orm(
1213 method = "name_create",
1214 args = ["name"],
1215 kwargs = [],
1216)]
1217#[derive(Debug)]
1218pub struct NameCreate {
1219 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
1220 pub database: String,
1221
1222 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
1223 pub uid: OdooId,
1224
1225 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
1226 pub password: String,
1227
1228 /// The Odoo model
1229 pub model: String,
1230
1231 /// A name for the new record
1232 pub name: String,
1233}
1234
1235#[derive(Debug, Serialize_tuple, Deserialize_tuple)]
1236pub struct NameCreateResponse {
1237 /// The record id
1238 pub id: OdooId,
1239
1240 /// The record `display_name`
1241 pub name: String,
1242}
1243
1244/// Search for records based on their `name` field
1245///
1246/// This is a shortcut to the `search()` method with only one domain component:
1247/// `[("name", "ilike", name)]`. This function is generally used by the Odoo searchbar,
1248/// and by the search function on x2many fields.
1249///
1250/// **Note**: Some models may override this method to provide custom "name search"
1251/// behaviour.
1252///
1253/// ## Example
1254/// ```no_run
1255/// # #[cfg(not(feature = "types-only"))]
1256/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
1257/// # use odoo_api::{OdooClient, jmap};
1258/// # let client = OdooClient::new_reqwest_blocking("")?;
1259/// # let mut client = client.authenticate_manual("", "", 1, "", None);
1260/// client.name_search(
1261/// "res.partner",
1262/// "Admini%".into(),
1263/// None,
1264/// None,
1265/// None,
1266/// ).send()?;
1267/// # Ok(())
1268/// # }
1269/// ```
1270/// <br />
1271///
1272/// See: [odoo/models.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/models.py#L1608-L1634)
1273#[odoo_orm(
1274 method = "name_search",
1275 args = ["name"],
1276 kwargs = ["args", "operator", "limit"],
1277)]
1278#[derive(Debug)]
1279pub struct NameSearch {
1280 /// The database name (auto-filled by [`OdooClient`](crate::client::OdooClient))
1281 pub database: String,
1282
1283 /// The user id (auto-filled by [`OdooClient`](crate::client::OdooClient))
1284 pub uid: OdooId,
1285
1286 /// The user password (auto-filled by [`OdooClient`](crate::client::OdooClient))
1287 pub password: String,
1288
1289 /// The Odoo model
1290 pub model: String,
1291
1292 /// The name to search for (can include operators like `%`)
1293 pub name: String,
1294
1295 /// An optional search domain
1296 pub args: Option<Vec<Value>>,
1297
1298 /// A domain operator for the "name test"
1299 ///
1300 /// For example:
1301 /// * `ilike` (default)
1302 /// * `=`
1303 /// * ...etc
1304 pub operator: Option<String>,
1305
1306 /// Limit the number of results
1307 pub limit: Option<u32>,
1308}
1309
1310#[derive(Debug, Serialize_tuple, Deserialize_tuple)]
1311#[serde(transparent)]
1312pub struct NameSearchResponse {
1313 pub records: Vec<NameSearchResponseItem>,
1314}
1315
1316#[derive(Debug, Serialize_tuple, Deserialize_tuple)]
1317pub struct NameSearchResponseItem {
1318 /// The record id
1319 pub id: OdooId,
1320
1321 /// The record `display_name`
1322 pub name: String,
1323}
1324
1325#[cfg(test)]
1326mod test {
1327 use super::*;
1328 use crate::client::error::Result;
1329 use crate::jsonrpc::{JsonRpcParams, JsonRpcResponse};
1330 use crate::{jmap, jvec, svec};
1331 use serde_json::{from_value, json, to_value};
1332
1333 #[test]
1334 fn create_one() -> Result<()> {
1335 let expected = json!({
1336 "jsonrpc": "2.0",
1337 "method": "call",
1338 "id": 1000,
1339 "params": {
1340 "service": "object",
1341 "method": "execute_kw",
1342 "args": [
1343 "some-database",
1344 2,
1345 "password",
1346 "res.partner",
1347 "create",
1348 [
1349 {"name": "Hello, world!"}
1350 ],
1351 {}
1352 ]
1353 }
1354 });
1355 let actual = to_value(
1356 Create {
1357 database: "some-database".into(),
1358 uid: 2,
1359 password: "password".into(),
1360
1361 model: "res.partner".into(),
1362 values: jmap! {"name": "Hello, world!"}.into(),
1363 }
1364 .build(1000),
1365 )?;
1366
1367 assert_eq!(actual, expected);
1368
1369 Ok(())
1370 }
1371
1372 #[test]
1373 fn create_one_response() -> Result<()> {
1374 let payload = json!({
1375 "jsonrpc": "2.0",
1376 "id": 1000,
1377 "result": 47
1378 });
1379
1380 let response: JsonRpcResponse<CreateResponse> = from_value(payload)?;
1381
1382 match response {
1383 JsonRpcResponse::Error(e) => Err(e.error.into()),
1384 JsonRpcResponse::Success(_) => Ok(()),
1385 }
1386 }
1387
1388 #[test]
1389 fn create_multi() -> Result<()> {
1390 let expected = json!({
1391 "jsonrpc": "2.0",
1392 "method": "call",
1393 "id": 1000,
1394 "params": {
1395 "service": "object",
1396 "method": "execute_kw",
1397 "args": [
1398 "some-database",
1399 2,
1400 "password",
1401 "res.partner",
1402 "create",
1403 [
1404 [
1405 {"name": "Hello, world!"},
1406 {"name": "Marco, polo!"}
1407 ]
1408 ],
1409 {}
1410 ]
1411 }
1412 });
1413 let actual = to_value(
1414 Create {
1415 database: "some-database".into(),
1416 uid: 2,
1417 password: "password".into(),
1418
1419 model: "res.partner".into(),
1420 values: jvec![
1421 {"name": "Hello, world!"},
1422 {"name": "Marco, polo!"}
1423 ]
1424 .into(),
1425 }
1426 .build(1000),
1427 )?;
1428
1429 assert_eq!(actual, expected);
1430
1431 Ok(())
1432 }
1433
1434 #[test]
1435 fn create_multi_response() -> Result<()> {
1436 let payload = json!({
1437 "jsonrpc": "2.0",
1438 "id": 1000,
1439 "result": [
1440 50,
1441 51
1442 ]
1443 });
1444
1445 let response: JsonRpcResponse<CreateResponse> = from_value(payload)?;
1446
1447 match response {
1448 JsonRpcResponse::Error(e) => Err(e.error.into()),
1449 JsonRpcResponse::Success(_) => Ok(()),
1450 }
1451 }
1452
1453 #[test]
1454 fn read() -> Result<()> {
1455 let expected = json!({
1456 "jsonrpc": "2.0",
1457 "method": "call",
1458 "id": 1000,
1459 "params": {
1460 "service": "object",
1461 "method": "execute_kw",
1462 "args": [
1463 "some-database",
1464 2,
1465 "password",
1466 "res.partner",
1467 "read",
1468 [
1469 [1, 2, 3]
1470 ],
1471 {
1472 "fields": ["id", "login"]
1473 }
1474 ]
1475 }
1476 });
1477 let actual = to_value(
1478 Read {
1479 database: "some-database".into(),
1480 uid: 2,
1481 password: "password".into(),
1482
1483 model: "res.partner".into(),
1484 ids: vec![1, 2, 3].into(),
1485 fields: svec!["id", "login"],
1486 }
1487 .build(1000),
1488 )?;
1489
1490 assert_eq!(actual, expected);
1491
1492 Ok(())
1493 }
1494
1495 #[test]
1496 fn read_response() -> Result<()> {
1497 let payload = json!({
1498 "jsonrpc": "2.0",
1499 "id": 1000,
1500 "result": [
1501 {
1502 "id": 1,
1503 "name": "My Company (San Francisco)"
1504 },
1505 {
1506 "id": 2,
1507 "name": "OdooBot"
1508 },
1509 {
1510 "id": 3,
1511 "name": "Administrator"
1512 }
1513 ]
1514 });
1515
1516 let response: JsonRpcResponse<ReadResponse> = from_value(payload)?;
1517
1518 match response {
1519 JsonRpcResponse::Error(e) => Err(e.error.into()),
1520 JsonRpcResponse::Success(_) => Ok(()),
1521 }
1522 }
1523
1524 #[test]
1525 fn write() -> Result<()> {
1526 let expected = json!({
1527 "jsonrpc": "2.0",
1528 "method": "call",
1529 "id": 1000,
1530 "params": {
1531 "service": "object",
1532 "method": "execute_kw",
1533 "args": [
1534 "some-database",
1535 2,
1536 "password",
1537 "res.partner",
1538 "write",
1539 [
1540 [2],
1541 {
1542 "name": "The Admin Account"
1543 }
1544 ],
1545 {}
1546 ]
1547 }
1548 });
1549 let actual = to_value(
1550 Write {
1551 database: "some-database".into(),
1552 uid: 2,
1553 password: "password".into(),
1554
1555 model: "res.partner".into(),
1556 ids: 2.into(),
1557 values: jmap! {"name": "The Admin Account"},
1558 }
1559 .build(1000),
1560 )?;
1561
1562 assert_eq!(actual, expected);
1563
1564 Ok(())
1565 }
1566
1567 #[test]
1568 fn write_response() -> Result<()> {
1569 let payload = json!({
1570 "jsonrpc": "2.0",
1571 "id": 1000,
1572 "result": true
1573 });
1574
1575 let response: JsonRpcResponse<WriteResponse> = from_value(payload)?;
1576
1577 match response {
1578 JsonRpcResponse::Error(e) => Err(e.error.into()),
1579 JsonRpcResponse::Success(_) => Ok(()),
1580 }
1581 }
1582
1583 #[test]
1584 fn unlink() -> Result<()> {
1585 let expected = json!({
1586 "jsonrpc": "2.0",
1587 "method": "call",
1588 "id": 1000,
1589 "params": {
1590 "service": "object",
1591 "method": "execute_kw",
1592 "args": [
1593 "some-database",
1594 2,
1595 "password",
1596 "res.partner",
1597 "unlink",
1598 [
1599 [3],
1600 ],
1601 {}
1602 ]
1603 }
1604 });
1605 let actual = to_value(
1606 Unlink {
1607 database: "some-database".into(),
1608 uid: 2,
1609 password: "password".into(),
1610
1611 model: "res.partner".into(),
1612 ids: 3.into(), // the "default" user - be careful!
1613 }
1614 .build(1000),
1615 )?;
1616
1617 assert_eq!(actual, expected);
1618
1619 Ok(())
1620 }
1621
1622 #[test]
1623 fn unlink_response() -> Result<()> {
1624 let payload = json!({
1625 "jsonrpc": "2.0",
1626 "id": 1000,
1627 "result": true
1628 });
1629
1630 let response: JsonRpcResponse<UnlinkResponse> = from_value(payload)?;
1631
1632 match response {
1633 JsonRpcResponse::Error(e) => Err(e.error.into()),
1634 JsonRpcResponse::Success(_) => Ok(()),
1635 }
1636 }
1637
1638 #[test]
1639 fn read_group() -> Result<()> {
1640 let expected = json!({
1641 "jsonrpc": "2.0",
1642 "method": "call",
1643 "id": 1000,
1644 "params": {
1645 "service": "object",
1646 "method": "execute_kw",
1647 "args": [
1648 "some-database",
1649 2,
1650 "password",
1651 "res.partner",
1652 "read_group",
1653 [
1654 [
1655 ["id", ">", 0]
1656 ],
1657 [
1658 "id",
1659 "name",
1660 "company_type"
1661 ],
1662 [
1663 "create_date:month",
1664 "company_id"
1665 ]
1666 ],
1667 {
1668 "offset": 0,
1669 "limit": 100,
1670 "orderby": "create_date desc",
1671 "lazy": false
1672 }
1673 ]
1674 }
1675 });
1676 let actual = to_value(
1677 ReadGroup {
1678 database: "some-database".into(),
1679 uid: 2,
1680 password: "password".into(),
1681
1682 model: "res.partner".into(),
1683
1684 domain: jvec![["id", ">", 0]],
1685 fields: svec!["id", "name", "company_type"],
1686 groupby: svec!["create_date:month", "company_id"],
1687 offset: Some(0),
1688 limit: Some(100),
1689 orderby: Some("create_date desc".into()),
1690 lazy: false,
1691 }
1692 .build(1000),
1693 )?;
1694
1695 assert_eq!(actual, expected);
1696
1697 Ok(())
1698 }
1699
1700 #[test]
1701 fn read_group_response() -> Result<()> {
1702 let payload = json!({
1703 "jsonrpc": "2.0",
1704 "id": 1000,
1705 "result": [
1706 {
1707 "__count": 5,
1708 "create_date:month": "January 2023",
1709 "company_id": false,
1710 "__domain": [
1711 "&",
1712 "&",
1713 "&",
1714 [
1715 "create_date",
1716 ">=",
1717 "2023-01-01 00:00:00"
1718 ],
1719 [
1720 "create_date",
1721 "<",
1722 "2023-02-01 00:00:00"
1723 ],
1724 [
1725 "company_id",
1726 "=",
1727 false
1728 ],
1729 [
1730 "id",
1731 ">",
1732 0
1733 ]
1734 ]
1735 },
1736 {
1737 "__count": 1,
1738 "create_date:month": "December 2022",
1739 "company_id": [
1740 1,
1741 "Test!"
1742 ],
1743 "__domain": [
1744 "&",
1745 "&",
1746 "&",
1747 [
1748 "create_date",
1749 ">=",
1750 "2022-12-01 00:00:00"
1751 ],
1752 [
1753 "create_date",
1754 "<",
1755 "2023-01-01 00:00:00"
1756 ],
1757 [
1758 "company_id",
1759 "=",
1760 1
1761 ],
1762 [
1763 "id",
1764 ">",
1765 0
1766 ]
1767 ]
1768 },
1769 ]
1770 });
1771
1772 let response: JsonRpcResponse<ReadGroupResponse> = from_value(payload)?;
1773
1774 match response {
1775 JsonRpcResponse::Error(e) => Err(e.error.into()),
1776 JsonRpcResponse::Success(_) => Ok(()),
1777 }
1778 }
1779
1780 #[test]
1781 fn search_read() -> Result<()> {
1782 let expected = json!({
1783 "jsonrpc": "2.0",
1784 "method": "call",
1785 "id": 1000,
1786 "params": {
1787 "service": "object",
1788 "method": "execute_kw",
1789 "args": [
1790 "some-database",
1791 2,
1792 "password",
1793 "res.partner",
1794 "search_read",
1795 [],
1796 {
1797 "domain": [
1798 ["company_type", "=", "company"]
1799 ],
1800 "fields": [
1801 "id",
1802 "name",
1803 "company_type"
1804 ],
1805 "offset": 0,
1806 "limit": 100,
1807 "order": "create_date desc"
1808 }
1809 ]
1810 }
1811 });
1812 let actual = to_value(
1813 SearchRead {
1814 database: "some-database".into(),
1815 uid: 2,
1816 password: "password".into(),
1817
1818 model: "res.partner".into(),
1819
1820 domain: jvec![["company_type", "=", "company"]],
1821 fields: svec!["id", "name", "company_type"],
1822 offset: Some(0),
1823 limit: Some(100),
1824 order: Some("create_date desc".into()),
1825 }
1826 .build(1000),
1827 )?;
1828
1829 assert_eq!(actual, expected);
1830
1831 Ok(())
1832 }
1833
1834 #[test]
1835 fn search_read_response() -> Result<()> {
1836 let payload = json!({
1837 "jsonrpc": "2.0",
1838 "id": 1000,
1839 "result": [
1840 {
1841 "id": 48,
1842 "name": "Partner #1",
1843 "company_type": "person"
1844 },
1845 {
1846 "id": 49,
1847 "name": "Partner #2",
1848 "company_type": "person"
1849 },
1850 ]
1851 });
1852
1853 let response: JsonRpcResponse<SearchReadResponse> = from_value(payload)?;
1854
1855 match response {
1856 JsonRpcResponse::Error(e) => Err(e.error.into()),
1857 JsonRpcResponse::Success(_) => Ok(()),
1858 }
1859 }
1860
1861 #[test]
1862 fn search() -> Result<()> {
1863 let expected = json!({
1864 "jsonrpc": "2.0",
1865 "method": "call",
1866 "id": 1000,
1867 "params": {
1868 "service": "object",
1869 "method": "execute_kw",
1870 "args": [
1871 "some-database",
1872 2,
1873 "password",
1874 "res.partner",
1875 "search",
1876 [
1877 [
1878 ["company_type", "=", "company"]
1879 ]
1880 ],
1881 {
1882 "offset": 0,
1883 "limit": 100,
1884 "order": "create_date desc"
1885 }
1886 ]
1887 }
1888 });
1889 let actual = to_value(
1890 Search {
1891 database: "some-database".into(),
1892 uid: 2,
1893 password: "password".into(),
1894
1895 model: "res.partner".into(),
1896
1897 domain: jvec![["company_type", "=", "company"]],
1898 offset: Some(0),
1899 limit: Some(100),
1900 order: Some("create_date desc".into()),
1901 }
1902 .build(1000),
1903 )?;
1904
1905 assert_eq!(actual, expected);
1906
1907 Ok(())
1908 }
1909
1910 #[test]
1911 fn search_response() -> Result<()> {
1912 let payload = json!({
1913 "jsonrpc": "2.0",
1914 "id": 1000,
1915 "result": [
1916 48,
1917 49,
1918 47,
1919 45,
1920 44,
1921 ]
1922 });
1923
1924 let response: JsonRpcResponse<SearchResponse> = from_value(payload)?;
1925
1926 match response {
1927 JsonRpcResponse::Error(e) => Err(e.error.into()),
1928 JsonRpcResponse::Success(_) => Ok(()),
1929 }
1930 }
1931
1932 #[test]
1933 fn search_count() -> Result<()> {
1934 let expected = json!({
1935 "jsonrpc": "2.0",
1936 "method": "call",
1937 "id": 1000,
1938 "params": {
1939 "service": "object",
1940 "method": "execute_kw",
1941 "args": [
1942 "some-database",
1943 2,
1944 "password",
1945 "res.partner",
1946 "search_count",
1947 [
1948 [
1949 ["company_type", "=", "company"]
1950 ]
1951 ],
1952 {
1953 "limit": null
1954 }
1955 ]
1956 }
1957 });
1958 let actual = to_value(
1959 SearchCount {
1960 database: "some-database".into(),
1961 uid: 2,
1962 password: "password".into(),
1963
1964 model: "res.partner".into(),
1965
1966 domain: jvec![["company_type", "=", "company"]],
1967 limit: None,
1968 }
1969 .build(1000),
1970 )?;
1971
1972 assert_eq!(actual, expected);
1973
1974 Ok(())
1975 }
1976
1977 #[test]
1978 fn search_count_response() -> Result<()> {
1979 let payload = json!({
1980 "jsonrpc": "2.0",
1981 "id": 1000,
1982 "result": 46
1983 });
1984
1985 let response: JsonRpcResponse<SearchCountResponse> = from_value(payload)?;
1986
1987 match response {
1988 JsonRpcResponse::Error(e) => Err(e.error.into()),
1989 JsonRpcResponse::Success(_) => Ok(()),
1990 }
1991 }
1992
1993 #[test]
1994 fn copy() -> Result<()> {
1995 let expected = json!({
1996 "jsonrpc": "2.0",
1997 "method": "call",
1998 "id": 1000,
1999 "params": {
2000 "service": "object",
2001 "method": "execute_kw",
2002 "args": [
2003 "some-database",
2004 2,
2005 "password",
2006 "res.partner",
2007 "copy",
2008 [
2009 2
2010 ],
2011 {
2012 "default": null
2013 }
2014 ]
2015 }
2016 });
2017 let actual = to_value(
2018 Copy {
2019 database: "some-database".into(),
2020 uid: 2,
2021 password: "password".into(),
2022
2023 model: "res.partner".into(),
2024
2025 id: 2,
2026 default: None,
2027 }
2028 .build(1000),
2029 )?;
2030
2031 assert_eq!(actual, expected);
2032
2033 Ok(())
2034 }
2035
2036 #[test]
2037 fn copy_response() -> Result<()> {
2038 let payload = json!({
2039 "jsonrpc": "2.0",
2040 "id": 1000,
2041 "result": 54
2042 });
2043
2044 let response: JsonRpcResponse<CopyResponse> = from_value(payload)?;
2045
2046 match response {
2047 JsonRpcResponse::Error(e) => Err(e.error.into()),
2048 JsonRpcResponse::Success(_) => Ok(()),
2049 }
2050 }
2051
2052 #[test]
2053 fn exists() -> Result<()> {
2054 let expected = json!({
2055 "jsonrpc": "2.0",
2056 "method": "call",
2057 "id": 1000,
2058 "params": {
2059 "service": "object",
2060 "method": "execute_kw",
2061 "args": [
2062 "some-database",
2063 2,
2064 "password",
2065 "res.partner",
2066 "exists",
2067 [
2068 [1, 2, -1, 999999999]
2069 ],
2070 {
2071 }
2072 ]
2073 }
2074 });
2075 let actual = to_value(
2076 Exists {
2077 database: "some-database".into(),
2078 uid: 2,
2079 password: "password".into(),
2080
2081 model: "res.partner".into(),
2082
2083 ids: vec![1, 2, -1, 999999999].into(),
2084 }
2085 .build(1000),
2086 )?;
2087
2088 assert_eq!(actual, expected);
2089
2090 Ok(())
2091 }
2092
2093 #[test]
2094 fn exists_response() -> Result<()> {
2095 let payload = json!({
2096 "jsonrpc": "2.0",
2097 "id": 1000,
2098 "result": [
2099 1,
2100 2
2101 ]
2102 });
2103
2104 let response: JsonRpcResponse<ExistsResponse> = from_value(payload)?;
2105
2106 match response {
2107 JsonRpcResponse::Error(e) => Err(e.error.into()),
2108 JsonRpcResponse::Success(_) => Ok(()),
2109 }
2110 }
2111
2112 #[test]
2113 fn check_access_rights() -> Result<()> {
2114 let expected = json!({
2115 "jsonrpc": "2.0",
2116 "method": "call",
2117 "id": 1000,
2118 "params": {
2119 "service": "object",
2120 "method": "execute_kw",
2121 "args": [
2122 "some-database",
2123 2,
2124 "password",
2125 "stock.quant",
2126 "check_access_rights",
2127 [
2128 "unlink"
2129 ],
2130 {
2131 "raise_exception": false
2132 }
2133 ]
2134 }
2135 });
2136 let actual = to_value(
2137 CheckAccessRights {
2138 database: "some-database".into(),
2139 uid: 2,
2140 password: "password".into(),
2141
2142 model: "stock.quant".into(),
2143
2144 operation: AccessOperation::Unlink,
2145 raise_exception: false,
2146 }
2147 .build(1000),
2148 )?;
2149
2150 assert_eq!(actual, expected);
2151
2152 Ok(())
2153 }
2154
2155 #[test]
2156 fn check_access_rights_response() -> Result<()> {
2157 let payload = json!({
2158 "jsonrpc": "2.0",
2159 "id": 1000,
2160 "result": false
2161 });
2162
2163 let response: JsonRpcResponse<CheckAccessRightsResponse> = from_value(payload)?;
2164
2165 match response {
2166 JsonRpcResponse::Error(e) => Err(e.error.into()),
2167 JsonRpcResponse::Success(_) => Ok(()),
2168 }
2169 }
2170
2171 #[test]
2172 fn check_access_rules() -> Result<()> {
2173 let expected = json!({
2174 "jsonrpc": "2.0",
2175 "method": "call",
2176 "id": 1000,
2177 "params": {
2178 "service": "object",
2179 "method": "execute_kw",
2180 "args": [
2181 "some-database",
2182 2,
2183 "password",
2184 "res.partner",
2185 "check_access_rule",
2186 [
2187 [1, 2],
2188 "unlink"
2189 ],
2190 {
2191 }
2192 ]
2193 }
2194 });
2195 let actual = to_value(
2196 CheckAccessRules {
2197 database: "some-database".into(),
2198 uid: 2,
2199 password: "password".into(),
2200
2201 model: "res.partner".into(),
2202
2203 ids: vec![1, 2].into(),
2204 operation: AccessOperation::Unlink,
2205 }
2206 .build(1000),
2207 )?;
2208
2209 assert_eq!(actual, expected);
2210
2211 Ok(())
2212 }
2213
2214 #[test]
2215 fn check_access_rules_response() -> Result<()> {
2216 //TODO: this method, annoyingly, returns None on success. because of this,
2217 // the `result` field is never added. we need to modify JsonRpcResponse to
2218 // support this. until then, this method does not work!
2219 let payload = json!({
2220 "jsonrpc": "2.0",
2221 "id": 1000,
2222 "result": [
2223 "id",
2224 "email",
2225 "this_is_a_fake_field"
2226 ]
2227 });
2228
2229 let response: JsonRpcResponse<CheckAccessRulesResponse> = {
2230 match from_value(payload) {
2231 Ok(d) => d,
2232 Err(_) => {
2233 // As a *super* hacky workaround, we can check for deserialization
2234 // errors
2235 return Ok(());
2236 }
2237 }
2238 };
2239
2240 match response {
2241 JsonRpcResponse::Error(e) => Err(e.error.into()),
2242 JsonRpcResponse::Success(_) => Ok(()),
2243 }
2244 }
2245
2246 #[test]
2247 fn check_field_access_rights() -> Result<()> {
2248 let expected = json!({
2249 "jsonrpc": "2.0",
2250 "method": "call",
2251 "id": 1000,
2252 "params": {
2253 "service": "object",
2254 "method": "execute_kw",
2255 "args": [
2256 "some-database",
2257 2,
2258 "password",
2259 "res.partner",
2260 "check_field_access_rights",
2261 [
2262 "unlink",
2263 [
2264 "id",
2265 "email",
2266 "this_is_a_fake_field"
2267 ]
2268 ],
2269 {
2270 }
2271 ]
2272 }
2273 });
2274 let actual = to_value(
2275 CheckFieldAccessRights {
2276 database: "some-database".into(),
2277 uid: 2,
2278 password: "password".into(),
2279
2280 model: "res.partner".into(),
2281
2282 operation: AccessOperation::Unlink,
2283 fields: svec!["id", "email", "this_is_a_fake_field"],
2284 }
2285 .build(1000),
2286 )?;
2287
2288 assert_eq!(actual, expected);
2289
2290 Ok(())
2291 }
2292
2293 #[test]
2294 fn check_field_access_rights_response() -> Result<()> {
2295 let payload = json!({
2296 "jsonrpc": "2.0",
2297 "id": 1000,
2298 "result": [
2299 "id",
2300 "email",
2301 "this_is_a_fake_field"
2302 ]
2303 });
2304
2305 let response: JsonRpcResponse<CheckFieldAccessRightsResponse> = from_value(payload)?;
2306
2307 match response {
2308 JsonRpcResponse::Error(e) => Err(e.error.into()),
2309 JsonRpcResponse::Success(_) => Ok(()),
2310 }
2311 }
2312
2313 #[test]
2314 fn get_metadata() -> Result<()> {
2315 let expected = json!({
2316 "jsonrpc": "2.0",
2317 "method": "call",
2318 "id": 1000,
2319 "params": {
2320 "service": "object",
2321 "method": "execute_kw",
2322 "args": [
2323 "some-database",
2324 2,
2325 "password",
2326 "res.partner",
2327 "get_metadata",
2328 [
2329 [1, 2]
2330 ],
2331 {
2332 }
2333 ]
2334 }
2335 });
2336 let actual = to_value(
2337 GetMetadata {
2338 database: "some-database".into(),
2339 uid: 2,
2340 password: "password".into(),
2341
2342 model: "res.partner".into(),
2343
2344 ids: vec![1, 2].into(),
2345 }
2346 .build(1000),
2347 )?;
2348
2349 assert_eq!(actual, expected);
2350
2351 Ok(())
2352 }
2353
2354 #[test]
2355 fn get_metadata_response() -> Result<()> {
2356 let payload = json!({
2357 "jsonrpc": "2.0",
2358 "id": 1000,
2359 "result": [
2360 {
2361 "id": 1,
2362 "create_uid": false,
2363 "create_date": "2022-09-15 20:00:41",
2364 "write_uid": [
2365 2,
2366 "Administrator"
2367 ],
2368 "write_date": "2023-01-16 01:17:19",
2369 "xmlid": "base.main_partner",
2370 "noupdate": true
2371 },
2372 {
2373 "id": 2,
2374 "create_uid": [
2375 1,
2376 "OdooBot"
2377 ],
2378 "create_date": "2022-09-15 20:00:43",
2379 "write_uid": [
2380 1,
2381 "OdooBot"
2382 ],
2383 "write_date": "2023-02-20 22:32:37",
2384 "xmlid": "base.partner_root",
2385 "noupdate": true
2386 }
2387 ]
2388 });
2389
2390 let response: JsonRpcResponse<GetMetadataResponse> = from_value(payload)?;
2391
2392 match response {
2393 JsonRpcResponse::Error(e) => Err(e.error.into()),
2394 JsonRpcResponse::Success(_) => Ok(()),
2395 }
2396 }
2397
2398 #[test]
2399 fn get_external_id() -> Result<()> {
2400 let expected = json!({
2401 "jsonrpc": "2.0",
2402 "method": "call",
2403 "id": 1000,
2404 "params": {
2405 "service": "object",
2406 "method": "execute_kw",
2407 "args": [
2408 "some-database",
2409 2,
2410 "password",
2411 "res.partner",
2412 "get_external_id",
2413 [
2414 [1, 2]
2415 ],
2416 {
2417 }
2418 ]
2419 }
2420 });
2421 let actual = to_value(
2422 GetExternalId {
2423 database: "some-database".into(),
2424 uid: 2,
2425 password: "password".into(),
2426
2427 model: "res.partner".into(),
2428
2429 ids: vec![1, 2].into(),
2430 }
2431 .build(1000),
2432 )?;
2433
2434 assert_eq!(actual, expected);
2435
2436 Ok(())
2437 }
2438
2439 #[test]
2440 fn get_external_id_response() -> Result<()> {
2441 let payload = json!({
2442 "jsonrpc": "2.0",
2443 "id": 1000,
2444 "result": {
2445 "1": "base.main_partner",
2446 "2": "base.partner_root"
2447 }
2448 });
2449
2450 let response: JsonRpcResponse<GetExternalIdResponse> = from_value(payload)?;
2451
2452 match response {
2453 JsonRpcResponse::Error(e) => Err(e.error.into()),
2454 JsonRpcResponse::Success(_) => Ok(()),
2455 }
2456 }
2457
2458 #[test]
2459 fn get_xml_id() -> Result<()> {
2460 let expected = json!({
2461 "jsonrpc": "2.0",
2462 "method": "call",
2463 "id": 1000,
2464 "params": {
2465 "service": "object",
2466 "method": "execute_kw",
2467 "args": [
2468 "some-database",
2469 2,
2470 "password",
2471 "res.partner",
2472 "get_xml_id",
2473 [
2474 [1, 2]
2475 ],
2476 {
2477 }
2478 ]
2479 }
2480 });
2481 let actual = to_value(
2482 GetXmlId {
2483 database: "some-database".into(),
2484 uid: 2,
2485 password: "password".into(),
2486
2487 model: "res.partner".into(),
2488
2489 ids: vec![1, 2].into(),
2490 }
2491 .build(1000),
2492 )?;
2493
2494 assert_eq!(actual, expected);
2495
2496 Ok(())
2497 }
2498
2499 #[test]
2500 fn get_xml_id_response() -> Result<()> {
2501 let payload = json!({
2502 "jsonrpc": "2.0",
2503 "id": 1000,
2504 "result": {
2505 "1": "base.main_partner",
2506 "2": "base.partner_root"
2507 }
2508 });
2509
2510 let response: JsonRpcResponse<GetXmlIdResponse> = from_value(payload)?;
2511
2512 match response {
2513 JsonRpcResponse::Error(e) => Err(e.error.into()),
2514 JsonRpcResponse::Success(_) => Ok(()),
2515 }
2516 }
2517
2518 #[test]
2519 fn name_get() -> Result<()> {
2520 let expected = json!({
2521 "jsonrpc": "2.0",
2522 "method": "call",
2523 "id": 1000,
2524 "params": {
2525 "service": "object",
2526 "method": "execute_kw",
2527 "args": [
2528 "some-database",
2529 2,
2530 "password",
2531 "res.partner",
2532 "name_get",
2533 [
2534 [1, 2, 3]
2535 ],
2536 {
2537 }
2538 ]
2539 }
2540 });
2541 let actual = to_value(
2542 NameGet {
2543 database: "some-database".into(),
2544 uid: 2,
2545 password: "password".into(),
2546
2547 model: "res.partner".into(),
2548
2549 ids: vec![1, 2, 3].into(),
2550 }
2551 .build(1000),
2552 )?;
2553
2554 assert_eq!(actual, expected);
2555
2556 Ok(())
2557 }
2558
2559 #[test]
2560 fn name_get_response() -> Result<()> {
2561 let payload = json!({
2562 "jsonrpc": "2.0",
2563 "id": 1000,
2564 "result": [
2565 [
2566 1,
2567 "Test!"
2568 ],
2569 [
2570 2,
2571 "OdooBot"
2572 ],
2573 [
2574 3,
2575 "YourCompany, Administrator"
2576 ]
2577 ]
2578 });
2579
2580 let response: JsonRpcResponse<NameGetResponse> = from_value(payload)?;
2581
2582 match response {
2583 JsonRpcResponse::Error(e) => Err(e.error.into()),
2584 JsonRpcResponse::Success(_) => Ok(()),
2585 }
2586 }
2587
2588 #[test]
2589 fn name_create() -> Result<()> {
2590 let expected = json!({
2591 "jsonrpc": "2.0",
2592 "method": "call",
2593 "id": 1000,
2594 "params": {
2595 "service": "object",
2596 "method": "execute_kw",
2597 "args": [
2598 "some-database",
2599 2,
2600 "password",
2601 "res.partner",
2602 "name_create",
2603 [
2604 "I am a test!"
2605 ],
2606 {
2607 }
2608 ]
2609 }
2610 });
2611 let actual = to_value(
2612 NameCreate {
2613 database: "some-database".into(),
2614 uid: 2,
2615 password: "password".into(),
2616
2617 model: "res.partner".into(),
2618
2619 name: "I am a test!".into(),
2620 }
2621 .build(1000),
2622 )?;
2623
2624 assert_eq!(actual, expected);
2625
2626 Ok(())
2627 }
2628
2629 #[test]
2630 fn name_create_response() -> Result<()> {
2631 let payload = json!({
2632 "jsonrpc": "2.0",
2633 "id": 1000,
2634 "result": [
2635 56,
2636 "I am a test!"
2637 ]
2638 });
2639
2640 let response: JsonRpcResponse<NameCreateResponse> = from_value(payload)?;
2641
2642 match response {
2643 JsonRpcResponse::Error(e) => Err(e.error.into()),
2644 JsonRpcResponse::Success(_) => Ok(()),
2645 }
2646 }
2647
2648 #[test]
2649 fn name_search() -> Result<()> {
2650 let expected = json!({
2651 "jsonrpc": "2.0",
2652 "method": "call",
2653 "id": 1000,
2654 "params": {
2655 "service": "object",
2656 "method": "execute_kw",
2657 "args": [
2658 "some-database",
2659 2,
2660 "password",
2661 "res.partner",
2662 "name_search",
2663 [
2664 "I am a test!"
2665 ],
2666 {
2667 "args": null,
2668 "operator": null,
2669 "limit": null,
2670 }
2671 ]
2672 }
2673 });
2674 let actual = to_value(
2675 NameSearch {
2676 database: "some-database".into(),
2677 uid: 2,
2678 password: "password".into(),
2679
2680 model: "res.partner".into(),
2681
2682 name: "I am a test!".into(),
2683 args: None,
2684 operator: None,
2685 limit: None,
2686 }
2687 .build(1000),
2688 )?;
2689
2690 assert_eq!(actual, expected);
2691
2692 Ok(())
2693 }
2694
2695 #[test]
2696 fn name_search_response() -> Result<()> {
2697 let payload = json!({
2698 "jsonrpc": "2.0",
2699 "id": 1000,
2700 "result": [
2701 [
2702 56,
2703 "I am a test!"
2704 ],
2705 [
2706 57,
2707 "I am a test!"
2708 ]
2709 ]
2710 });
2711
2712 let response: JsonRpcResponse<NameSearchResponse> = from_value(payload)?;
2713
2714 match response {
2715 JsonRpcResponse::Error(e) => Err(e.error.into()),
2716 JsonRpcResponse::Success(_) => Ok(()),
2717 }
2718 }
2719}