odoo_api/service/db.rs
1//! The Odoo "db" service (JSON-RPC)
2//!
3//! This service handles database-management related methods (like create, drop, etc)
4//!
5//! Note that you will see some methods that require a `passwd` argument. This is **not**
6//! the Odoo user password (database-level). Instead, it's the Odoo server-level
7//! "master password", which can be found in the Odoo `.conf` file as the `admin_passwd` key.
8
9use crate as odoo_api;
10use crate::jsonrpc::OdooApiMethod;
11use odoo_api_macros::odoo_api;
12use serde::de::Visitor;
13use serde::ser::SerializeTuple;
14use serde::{Deserialize, Serialize};
15use serde_tuple::Serialize_tuple;
16
17/// Create and initialize a new database
18///
19/// Note that this request may take some time to complete, and it's likely
20/// worth only firing this from an async-type client
21///
22/// ## Example
23/// ```no_run
24/// # #[cfg(not(feature = "types-only"))]
25/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
26/// # use odoo_api::OdooClient;
27/// # let client = OdooClient::new_reqwest_blocking("")?;
28/// # let mut client = client.authenticate_manual("", "", 1, "", None);
29/// let resp = client.db_create_database(
30/// "master-password",
31/// "new-database-name",
32/// false, // demo
33/// "en_GB", // lang
34/// "password1",// user password
35/// "admin", // username
36/// Some("gb".into()), // country
37/// None // phone
38/// ).send()?;
39/// # Ok(())
40/// # }
41/// ```
42///<br />
43///
44/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L136-L142)
45#[odoo_api(
46 service = "db",
47 method = "create_database",
48 name = "db_create_database",
49 auth = false
50)]
51#[derive(Debug, Serialize_tuple)]
52pub struct CreateDatabase {
53 /// The Odoo master password
54 pub passwd: String,
55
56 /// The name for the new database
57 pub db_name: String,
58
59 /// Should demo data be included?
60 pub demo: bool,
61
62 /// What language should be installed?
63 ///
64 /// This should be an "ISO" formatted string, e.g., "en_US" or "en_GB".
65 ///
66 /// See also: [`ListLang`]
67 pub lang: String,
68
69 /// A password for the "admin" user
70 pub user_password: String,
71
72 /// A login/username for the "admin" user
73 pub login: String,
74
75 /// Optionally specify a country
76 ///
77 /// This is used as a default for the default company created when the database
78 /// is initialised.
79 ///
80 /// See also: [`ListCountries`]
81 pub country_code: Option<String>,
82
83 /// Optionally specify a phone number
84 ///
85 /// As with `country_code`, this is used as a default for the newly-created
86 /// company.
87 pub phone: Option<String>,
88}
89
90/// The response to a [`CreateDatabase`] request
91#[derive(Debug, Serialize, Deserialize)]
92#[serde(transparent)]
93pub struct CreateDatabaseResponse {
94 pub ok: bool,
95}
96
97/// Duplicate a database
98///
99/// Note that this request may take some time to complete, and it's likely
100/// worth only firing this from an async-type client
101///
102/// ## Example
103/// ```no_run
104/// # #[cfg(not(feature = "types-only"))]
105/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
106/// # use odoo_api::OdooClient;
107/// # let client = OdooClient::new_reqwest_blocking("")?;
108/// # let mut client = client.authenticate_manual("", "", 1, "", None);
109/// let resp = client.db_duplicate_database(
110/// "master-password",
111/// "old-database",
112/// "new-database"
113/// ).send()?;
114/// # Ok(())
115/// # }
116/// ```
117///<br />
118///
119/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L144-L184)
120#[odoo_api(
121 service = "db",
122 method = "duplicate_database",
123 name = "db_duplicate_database",
124 auth = false
125)]
126#[derive(Debug, Serialize_tuple)]
127pub struct DuplicateDatabase {
128 /// The Odoo master password
129 pub passwd: String,
130
131 /// The original DB name (copy source)
132 pub db_original_name: String,
133
134 /// The new DB name (copy dest)
135 pub db_name: String,
136}
137
138/// The response to a [`DuplicateDatabase`] request
139#[derive(Debug, Serialize, Deserialize)]
140#[serde(transparent)]
141pub struct DuplicateDatabaseResponse {
142 pub ok: bool,
143}
144
145/// Drop (delete) a database
146///
147/// Note that this request may take some time to complete, and it's likely
148/// worth only firing this from an async-type client
149///
150/// ## Example
151/// ```no_run
152/// # #[cfg(not(feature = "types-only"))]
153/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
154/// # use odoo_api::OdooClient;
155/// # let client = OdooClient::new_reqwest_blocking("")?;
156/// # let mut client = client.authenticate_manual("", "", 1, "", None);
157/// let resp = client.db_drop(
158/// "master-password",
159/// "database-to-delete",
160/// ).send()?;
161/// # Ok(())
162/// # }
163/// ```
164///<br />
165///
166/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L212-L217)
167#[odoo_api(service = "db", method = "drop", name = "db_drop", auth = false)]
168#[derive(Debug, Serialize_tuple)]
169pub struct Drop {
170 /// The Odoo master password
171 pub passwd: String,
172
173 /// The database to be deleted
174 pub db_name: String,
175}
176
177/// The response to a [`Drop`] request
178#[derive(Debug, Serialize, Deserialize)]
179#[serde(transparent)]
180pub struct DropResponse {
181 pub ok: bool,
182}
183
184/// Dump (backup) a database, optionally including the filestore folder
185///
186/// Note that this request may take some time to complete, and it's likely
187/// worth only firing this from an async-type client
188///
189/// Note that the data is returned a base64-encoded buffer.
190///
191/// ## Example
192/// ```no_run
193/// # #[cfg(not(feature = "types-only"))]
194/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
195/// # use odoo_api::OdooClient;
196/// # let client = OdooClient::new_reqwest_blocking("")?;
197/// # let mut client = client.authenticate_manual("", "", 1, "", None);
198/// # #[allow(non_camel_case_types)]
199/// # struct base64 {}
200/// # impl base64 { fn decode(input: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> { Ok(Vec::new()) }}
201/// use odoo_api::service::db::DumpFormat;
202///
203/// let resp = client.db_dump(
204/// "master-password",
205/// "database-to-dump",
206/// DumpFormat::Zip
207/// ).send()?;
208///
209/// // parse the returned b64 string into a byte array
210/// // e.g., with the `base64` crate: https://docs.rs/base64/latest/base64/
211/// let data: Vec<u8> = base64::decode(&resp.b64_bytes)?;
212///
213/// // write the data to a file ...
214/// # Ok(())
215/// # }
216/// ```
217///<br />
218///
219/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L212-L217)
220/// See also: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L219-L269)
221#[odoo_api(service = "db", method = "dump", name = "db_dump", auth = false)]
222#[derive(Debug, Serialize_tuple)]
223pub struct Dump {
224 /// The Odoo master password
225 pub passwd: String,
226
227 /// The database to be backed-up
228 pub db_name: String,
229
230 /// The dump format. See [`DumpFormat`] for more info
231 pub format: crate::service::db::DumpFormat,
232}
233
234/// The format for a database dump
235#[derive(Debug, Serialize, Deserialize)]
236pub enum DumpFormat {
237 /// Output a zipfile containing the SQL dump in "plain" format, manifest, and filestore
238 ///
239 /// Note that with this mode, the database is dumped to a Python
240 /// NamedTemporaryFile first, then to the out stream - this means that
241 /// the backup takes longer, and probably involves some filesystem writes.
242 ///
243 /// Also note that the SQL format is "plain"; that is, it's a text file
244 /// containing SQL statements. This style of database dump is slightly less
245 /// flexible when importing (e.g., you cannot choose to exclude some
246 /// tables during import).
247 ///
248 /// See the [Postgres `pg_dump` docs](https://www.postgresql.org/docs/current/app-pgdump.html) for more info on "plain" dumps (`-F` option).
249 #[serde(rename = "zip")]
250 Zip,
251
252 /// Output a `.dump` file containing the SQL dump in "custom" format
253 ///
254 /// This style of database dump is more flexible on the import side (e.g.,
255 /// you can choose to exclude some tables from the import), but does not
256 /// include the filestore.
257 ///
258 /// See the [Postgres `pg_dump` docs](https://www.postgresql.org/docs/current/app-pgdump.html) for more info on "custom" dumps (`-F` option).
259 #[serde(rename = "dump")]
260 Dump,
261}
262
263/// The response to a [`Dump`] request
264#[derive(Debug, Serialize, Deserialize)]
265#[serde(transparent)]
266pub struct DumpResponse {
267 /// The database dump, as a base-64 encoded string
268 ///
269 /// Note that the file type will depend on the `format` used in the original request:
270 /// - [`DumpFormat::Zip`]: `backup.zip`
271 /// - [`DumpFormat::Dump`]: `backup.dump` (text file containig SQL CREATE/INSERT/etc statements )
272 pub b64_bytes: String,
273}
274
275/// Upload and restore an Odoo dump to a new database
276///
277/// Note that this request may take some time to complete, and it's likely
278/// worth only firing this from an async-type client
279///
280/// Note also that the uploaded "file" must:
281/// - Be a zip file
282/// - Contain a folder named `filestore`, whose direct descendents are the databases filestore content (e.g. `filestore/a0`, `filestore/a1`, etc)
283/// - Contain a file name `dump.sql`, which is a `pg_dump` "plain" format dump (e.g. a text file of SQL statements)
284///
285/// Typically Odoo backups also include a `manifest.json`, but this file isn't checked
286/// by the Restore endpoint.
287///
288/// ## Example
289/// ```no_run
290/// # #[cfg(not(feature = "types-only"))]
291/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
292/// # use odoo_api::OdooClient;
293/// # let client = OdooClient::new_reqwest_blocking("")?;
294/// # let mut client = client.authenticate_manual("", "", 1, "", None);
295/// # #[allow(non_camel_case_types)]
296/// # struct base64 {}
297/// # impl base64 { fn encode(data: &Vec<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> { Ok(data.to_owned()) }}
298/// use odoo_api::service::db::RestoreType;
299/// use std::fs;
300///
301/// // load the file data
302/// let data = fs::read("/my/database/backup.zip")?;
303///
304/// // convert raw bytes to base64
305/// // e.g., with the `base64` crate: https://crates.io/crates/base64
306/// let data_b64 = base64::encode(&data)?;
307///
308/// // convert base64's `Vec<u8>` to a `&str`
309/// let data_b64 = std::str::from_utf8(&data_b64)?;
310///
311/// // read `id` and `login` from users id=1,2,3
312/// client.db_restore(
313/// "master-password",
314/// data_b64,
315/// RestoreType::Copy
316/// ).send()?;
317/// # Ok(())
318/// # }
319/// ```
320///<br />
321///
322/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L271-L284)
323/// See also: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L286-L335)
324#[odoo_api(service = "db", method = "restore", name = "db_restore", auth = false)]
325#[derive(Debug, Serialize_tuple)]
326pub struct Restore {
327 /// The Odoo master password
328 pub passwd: String,
329
330 /// The backup data, as a base64-encoded string
331 pub b64_data: String,
332
333 /// The restore type (see [`RestoreType`])
334 pub restore_type: RestoreType,
335}
336
337/// The type of database restore
338#[derive(Debug)]
339pub enum RestoreType {
340 /// Restore as a "copy"
341 ///
342 /// In this case, the database UUID is automatically updated to prevent
343 /// conflicts.
344 ///
345 /// This is typically used when restoring a database for testing.
346 Copy,
347
348 /// Restore as a "move"
349 ///
350 /// In this case, the database UUID is **not** updated, and the database
351 /// is restored as-is.
352 ///
353 /// This is typically used when restoring a database to a new hosting environment.
354 Move,
355}
356
357// As far as I can tell, there isn't an easy way to serialize/deserialize
358// a two-variant enum to/from a boolean, so we need to implement those manually.
359// note that Deserialize isn't strictly necessary, but I'll include it for
360// completeness.
361impl Serialize for RestoreType {
362 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
363 where
364 S: serde::Serializer,
365 {
366 serializer.serialize_bool(match self {
367 Self::Copy => true,
368 Self::Move => false,
369 })
370 }
371}
372struct RestoreTypeVisitor;
373impl<'de> Visitor<'de> for RestoreTypeVisitor {
374 type Value = bool;
375
376 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
377 formatter.write_str("a boolean (`true` or `false`)")
378 }
379
380 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
381 where
382 E: serde::de::Error,
383 {
384 Ok(v)
385 }
386}
387impl<'de> Deserialize<'de> for RestoreType {
388 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
389 where
390 D: serde::Deserializer<'de>,
391 {
392 let b = deserializer.deserialize_bool(RestoreTypeVisitor)?;
393
394 Ok(match b {
395 true => Self::Copy,
396 false => Self::Move,
397 })
398 }
399}
400
401/// The response to a [`Restore`] request
402#[derive(Debug, Serialize, Deserialize)]
403#[serde(transparent)]
404pub struct RestoreResponse {
405 pub ok: bool,
406}
407
408/// Rename a database
409///
410/// On the Odoo side, this is handled by issuing an SQL query like:
411/// ```sql
412/// ALTER DATABSE {old_name} RENAME TO {new_name};
413/// ```
414///
415/// It should be a fairly quick request, but note that the above `ALTER DATABASE` statement
416/// may fail for various reasons. See the Postgres documentation for info.
417///
418/// ## Example
419/// ```no_run
420/// # #[cfg(not(feature = "types-only"))]
421/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
422/// # use odoo_api::OdooClient;
423/// # let client = OdooClient::new_reqwest_blocking("")?;
424/// # let mut client = client.authenticate_manual("", "", 1, "", None);
425/// let resp = client.db_rename(
426/// "master-password",
427/// "old-database-name",
428/// "new-database-name",
429/// ).send()?;
430/// # Ok(())
431/// # }
432/// ```
433///<br />
434///
435/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L337-L358)
436#[odoo_api(service = "db", method = "rename", name = "db_rename", auth = false)]
437#[derive(Debug, Serialize_tuple)]
438pub struct Rename {
439 /// The Odoo master password
440 pub passwd: String,
441
442 /// The database name
443 pub old_name: String,
444
445 /// The new database name
446 pub new_name: String,
447}
448
449/// The response to a [`Rename`] request
450#[derive(Debug, Serialize, Deserialize)]
451#[serde(transparent)]
452pub struct RenameResponse {
453 pub ok: bool,
454}
455
456/// Change the Odoo "master password"
457///
458/// This method updates the Odoo config file, writing a new value to the `admin_passwd`
459/// key. If the config file is not writeable by Odoo, this will fail.
460///
461/// ## Example
462/// ```no_run
463/// # #[cfg(not(feature = "types-only"))]
464/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
465/// # use odoo_api::OdooClient;
466/// # let client = OdooClient::new_reqwest_blocking("")?;
467/// # let mut client = client.authenticate_manual("", "", 1, "", None);
468/// let resp = client.db_change_admin_password(
469/// "master-password",
470/// "new-master-password",
471/// ).send()?;
472/// # Ok(())
473/// # }
474/// ```
475///<br />
476///
477/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L360-L364)
478#[odoo_api(
479 service = "db",
480 method = "change_admin_password",
481 name = "db_change_admin_password",
482 auth = false
483)]
484#[derive(Debug, Serialize_tuple)]
485pub struct ChangeAdminPassword {
486 /// The Odoo master password
487 pub passwd: String,
488
489 /// The new Odoo master password
490 pub new_passwd: String,
491}
492
493/// The response to a [`ChangeAdminPassword`] request
494#[derive(Debug, Serialize, Deserialize)]
495#[serde(transparent)]
496pub struct ChangeAdminPasswordResponse {
497 pub ok: bool,
498}
499
500/// Perform a "database migration" (upgrade the `base` module)
501///
502/// Note that this method doesn't actually perform any upgrades - instead, it
503/// force-update the `base` module, which has the effect of triggering an update
504/// on all Odoo modules that depend on `base` (which is all of them).
505///
506/// This method is probably used internally by Odoo's upgrade service, and likely
507/// isn't useful on its own. If you need to upgrade a module, the [`Execute`][crate::service::object::Execute]
508/// is probably more suitable.
509///
510/// ## Example
511/// ```no_run
512/// # #[cfg(not(feature = "types-only"))]
513/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
514/// # use odoo_api::OdooClient;
515/// # let client = OdooClient::new_reqwest_blocking("")?;
516/// # let mut client = client.authenticate_manual("", "", 1, "", None);
517/// let resp = client.db_migrate_databases(
518/// "master-password",
519/// vec![
520/// "database1".into(),
521/// "database2".into()
522/// ]
523/// ).send()?;
524/// # Ok(())
525/// # }
526/// ```
527///<br />
528///
529/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L366-L372)
530#[odoo_api(
531 service = "db",
532 method = "migrate_databases",
533 name = "db_migrate_databases",
534 auth = false
535)]
536#[derive(Debug, Serialize_tuple)]
537pub struct MigrateDatabases {
538 /// The Odoo master password
539 pub passwd: String,
540
541 /// A list of databases to be migrated
542 pub databases: Vec<String>,
543}
544
545/// The response to a [`MigrateDatabases`] request
546#[derive(Debug, Serialize, Deserialize)]
547#[serde(transparent)]
548pub struct MigrateDatabasesResponse {
549 pub ok: bool,
550}
551
552/// Check if a database exists
553///
554/// ## Example
555/// ```no_run
556/// # #[cfg(not(feature = "types-only"))]
557/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
558/// # use odoo_api::OdooClient;
559/// # let client = OdooClient::new_reqwest_blocking("")?;
560/// # let mut client = client.authenticate_manual("", "", 1, "", None);
561/// let resp = client.db_exist(
562/// "does-this-database-exist?",
563/// ).send()?;
564/// # Ok(())
565/// # }
566/// ```
567///<br />
568///
569/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L378-L386)
570#[odoo_api(service = "db", method = "db_exist", auth = false)]
571#[derive(Debug, Serialize_tuple)]
572pub struct DbExist {
573 /// The database name to check
574 pub db_name: String,
575}
576
577/// The response to a [`DbExist`] request
578#[derive(Debug, Serialize, Deserialize)]
579#[serde(transparent)]
580pub struct DbExistResponse {
581 pub exists: bool,
582}
583
584/// List the databases currently available to Odoo
585///
586/// ## Example
587/// ```no_run
588/// # #[cfg(not(feature = "types-only"))]
589/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
590/// # use odoo_api::OdooClient;
591/// # let client = OdooClient::new_reqwest_blocking("")?;
592/// # let mut client = client.authenticate_manual("", "", 1, "", None);
593/// let resp = client.db_list(false).send()?;
594///
595/// println!("Databases: {:#?}", resp.databases);
596/// # Ok(())
597/// # }
598/// ```
599///<br />
600///
601/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L439-L442)
602/// See also: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L388-L409)
603#[odoo_api(service = "db", method = "list", name = "db_list", auth = false)]
604#[derive(Debug, Serialize_tuple)]
605pub struct List {
606 /// This argument isn't currently used and has no effect on the output
607 pub document: bool,
608}
609
610/// The response to a [`List`] request
611#[derive(Debug, Serialize, Deserialize)]
612#[serde(transparent)]
613pub struct ListResponse {
614 pub databases: Vec<String>,
615}
616
617/// List the languages available to Odoo (ISO name + code)
618///
619/// Note that this function is used by the database manager, in order to let the
620/// user select which language should be used when creating a new database.
621///
622/// ## Example
623/// ```no_run
624/// # #[cfg(not(feature = "types-only"))]
625/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
626/// # use odoo_api::OdooClient;
627/// # let client = OdooClient::new_reqwest_blocking("")?;
628/// # let mut client = client.authenticate_manual("", "", 1, "", None);
629/// let resp = client.db_list_lang().send()?;
630///
631/// println!("Languages: {:#?}", resp.languages);
632/// # Ok(())
633/// # }
634/// ```
635///<br />
636///
637/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L444-L445)
638#[odoo_api(
639 service = "db",
640 method = "list_lang",
641 name = "db_list_lang",
642 auth = false
643)]
644#[derive(Debug)]
645pub struct ListLang {}
646
647// ListLang has no fields, but needs to output in JSON: `[]`
648impl Serialize for ListLang {
649 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
650 where
651 S: serde::Serializer,
652 {
653 let state = serializer.serialize_tuple(0)?;
654 state.end()
655 }
656}
657
658/// The response to a [`ListLang`] request
659#[derive(Debug, Serialize, Deserialize)]
660#[serde(transparent)]
661pub struct ListLangResponse {
662 pub languages: Vec<ListLangResponseItem>,
663}
664
665/// A single language item from the [`ListLang`] request
666#[derive(Debug, Serialize_tuple, Deserialize)]
667pub struct ListLangResponseItem {
668 /// The ISO language code (e.g., `en_GB`)
669 pub code: String,
670
671 /// The "pretty" language name
672 ///
673 /// This is formatted as: `english_pretty_name / local_name`
674 ///
675 /// Examples:
676 /// - `Danish / Dansk`
677 /// - `English (UK)`
678 /// - `Chinese (Simplified) / 简体中文`
679 pub name: String,
680}
681
682/// List the countries available to Odoo (ISO name + code)
683///
684/// Note that this function is used by the database manager, in order to let the
685/// user select which country should be used when creating a new database.
686///
687/// ## Example
688/// ```no_run
689/// # #[cfg(not(feature = "types-only"))]
690/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
691/// # use odoo_api::OdooClient;
692/// # let client = OdooClient::new_reqwest_blocking("")?;
693/// # let mut client = client.authenticate_manual("", "", 1, "", None);
694/// let resp = client.db_list_countries(
695/// "master-password",
696/// ).send()?;
697///
698/// println!("Countries: {:#?}", resp.countries);
699/// # Ok(())
700/// # }
701/// ```
702///<br />
703///
704/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L447-L454)
705#[odoo_api(
706 service = "db",
707 method = "list_countries",
708 name = "db_list_countries",
709 auth = false
710)]
711#[derive(Debug, Serialize_tuple)]
712pub struct ListCountries {
713 /// The Odoo master password
714 pub passwd: String,
715}
716
717/// The response to a [`ListCountries`] request
718#[derive(Debug, Serialize, Deserialize)]
719#[serde(transparent)]
720pub struct ListCountriesResponse {
721 pub countries: Vec<ListLangResponseItem>,
722}
723
724/// A single country item from the [`ListCountries`] request
725#[derive(Debug, Serialize_tuple, Deserialize)]
726pub struct ListCountriesResponseItem {
727 /// The ISO country code
728 pub code: String,
729
730 /// An English "pretty" representation of the country name, e.g.:
731 /// - `Afghanistan`
732 /// - `China`
733 /// - `New Zealand`
734 pub name: String,
735}
736
737/// Return the server version
738///
739/// This returns the "base" server version, e.g., `14.0` or `15.0`. It does not
740/// include any indication of whether the database is Community or Enterprise
741///
742/// ## Example
743/// ```no_run
744/// # #[cfg(not(feature = "types-only"))]
745/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
746/// # use odoo_api::OdooClient;
747/// # let client = OdooClient::new_reqwest_blocking("")?;
748/// # let mut client = client.authenticate_manual("", "", 1, "", None);
749/// let resp = client.db_server_version().send()?;
750///
751/// println!("Version: {}", resp.version);
752/// # Ok(())
753/// # }
754/// ```
755///<br />
756///
757/// Reference: [odoo/service/db.py](https://github.com/odoo/odoo/blob/b6e195ccb3a6c37b0d980af159e546bdc67b1e42/odoo/service/db.py#L456-L460)
758#[odoo_api(
759 service = "db",
760 method = "server_version",
761 name = "db_server_version",
762 auth = false
763)]
764#[derive(Debug)]
765pub struct ServerVersion {}
766
767// ServerVersion has no fields, but needs to output in JSON: `[]`
768impl Serialize for ServerVersion {
769 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
770 where
771 S: serde::Serializer,
772 {
773 let state = serializer.serialize_tuple(0)?;
774 state.end()
775 }
776}
777
778/// The response to a [`ServerVersion`] request
779#[derive(Debug, Serialize, Deserialize)]
780#[serde(transparent)]
781pub struct ServerVersionResponse {
782 /// The database version, e.g., `14.0` or `15.0`
783 pub version: String,
784}
785
786#[cfg(test)]
787mod test {
788 use super::*;
789 use crate::client::error::Result;
790 use crate::jsonrpc::{JsonRpcParams, JsonRpcResponse};
791 use serde_json::{from_value, json, to_value};
792
793 /// See [`crate::service::object::test::execute`] for more info
794 #[test]
795 fn create_database() -> Result<()> {
796 let expected = json!({
797 "jsonrpc": "2.0",
798 "method": "call",
799 "id": 1000,
800 "params": {
801 "service": "db",
802 "method": "create_database",
803 "args": [
804 "master-password",
805 "new-database",
806 false,
807 "en_US",
808 "password",
809 "admin",
810 null,
811 "123 123 123"
812 ]
813 }
814 });
815 let actual = to_value(
816 CreateDatabase {
817 passwd: "master-password".into(),
818 db_name: "new-database".into(),
819 demo: false,
820 lang: "en_US".into(),
821 user_password: "password".into(),
822 login: "admin".into(),
823 country_code: None,
824 phone: Some("123 123 123".into()),
825 }
826 .build(1000),
827 )?;
828
829 assert_eq!(actual, expected);
830
831 Ok(())
832 }
833
834 /// See [`crate::service::object::test::execute_response`] for more info
835 #[test]
836 fn create_database_response() -> Result<()> {
837 let payload = json!({
838 "jsonrpc": "2.0",
839 "id": 1000,
840 "result": true
841 });
842
843 let response: JsonRpcResponse<CreateDatabaseResponse> = from_value(payload)?;
844 match response {
845 JsonRpcResponse::Error(e) => Err(e.error.into()),
846 JsonRpcResponse::Success(_) => Ok(()),
847 }
848 }
849
850 /// See [`crate::service::object::test::execute`] for more info
851 #[test]
852 fn duplicate_database() -> Result<()> {
853 let expected = json!({
854 "jsonrpc": "2.0",
855 "method": "call",
856 "id": 1000,
857 "params": {
858 "service": "db",
859 "method": "duplicate_database",
860 "args": [
861 "master-password",
862 "old-database",
863 "new-database",
864 ]
865 }
866 });
867 let actual = to_value(
868 DuplicateDatabase {
869 passwd: "master-password".into(),
870 db_original_name: "old-database".into(),
871 db_name: "new-database".into(),
872 }
873 .build(1000),
874 )?;
875
876 assert_eq!(actual, expected);
877
878 Ok(())
879 }
880
881 /// See [`crate::service::object::test::execute_response`] for more info
882 #[test]
883 fn duplicate_database_response() -> Result<()> {
884 let payload = json!({
885 "jsonrpc": "2.0",
886 "id": 1000,
887 "result": true
888 });
889
890 let response: JsonRpcResponse<DuplicateDatabaseResponse> = from_value(payload)?;
891 match response {
892 JsonRpcResponse::Error(e) => Err(e.error.into()),
893 JsonRpcResponse::Success(_) => Ok(()),
894 }
895 }
896
897 /// See [`crate::service::object::test::execute`] for more info
898 #[test]
899 fn drop() -> Result<()> {
900 let expected = json!({
901 "jsonrpc": "2.0",
902 "method": "call",
903 "id": 1000,
904 "params": {
905 "service": "db",
906 "method": "drop",
907 "args": [
908 "master-password",
909 "old-database",
910 ]
911 }
912 });
913 let actual = to_value(
914 Drop {
915 passwd: "master-password".into(),
916 db_name: "old-database".into(),
917 }
918 .build(1000),
919 )?;
920
921 assert_eq!(actual, expected);
922
923 Ok(())
924 }
925
926 /// See [`crate::service::object::test::execute_response`] for more info
927 #[test]
928 fn drop_response() -> Result<()> {
929 let payload = json!({
930 "jsonrpc": "2.0",
931 "id": 1000,
932 "result": true
933 });
934
935 let response: JsonRpcResponse<DropResponse> = from_value(payload)?;
936 match response {
937 JsonRpcResponse::Error(e) => Err(e.error.into()),
938 JsonRpcResponse::Success(_) => Ok(()),
939 }
940 }
941
942 /// See [`crate::service::object::test::execute`] for more info
943 #[test]
944 fn dump_zip() -> Result<()> {
945 let expected = json!({
946 "jsonrpc": "2.0",
947 "method": "call",
948 "id": 1000,
949 "params": {
950 "service": "db",
951 "method": "dump",
952 "args": [
953 "master-password",
954 "old-database",
955 "zip",
956 ]
957 }
958 });
959 let actual = to_value(
960 Dump {
961 passwd: "master-password".into(),
962 db_name: "old-database".into(),
963 format: DumpFormat::Zip,
964 }
965 .build(1000),
966 )?;
967
968 assert_eq!(actual, expected);
969
970 Ok(())
971 }
972
973 /// See [`crate::service::object::test::execute`] for more info
974 #[test]
975 fn dump_dump() -> Result<()> {
976 let expected = json!({
977 "jsonrpc": "2.0",
978 "method": "call",
979 "id": 1000,
980 "params": {
981 "service": "db",
982 "method": "dump",
983 "args": [
984 "master-password",
985 "old-database",
986 "dump",
987 ]
988 }
989 });
990 let actual = to_value(
991 Dump {
992 passwd: "master-password".into(),
993 db_name: "old-database".into(),
994 format: DumpFormat::Dump,
995 }
996 .build(1000),
997 )?;
998
999 assert_eq!(actual, expected);
1000
1001 Ok(())
1002 }
1003
1004 /// See [`crate::service::object::test::execute_response`] for more info
1005 #[test]
1006 fn dump_response() -> Result<()> {
1007 let payload = json!({
1008 "jsonrpc": "2.0",
1009 "id": 1000,
1010 "result": "base64-data-will-be-here"
1011 });
1012
1013 let response: JsonRpcResponse<DumpResponse> = from_value(payload)?;
1014 match response {
1015 JsonRpcResponse::Error(e) => Err(e.error.into()),
1016 JsonRpcResponse::Success(_) => Ok(()),
1017 }
1018 }
1019
1020 /// See [`crate::service::object::test::execute`] for more info
1021 #[test]
1022 fn restore_move() -> Result<()> {
1023 let expected = json!({
1024 "jsonrpc": "2.0",
1025 "method": "call",
1026 "id": 1000,
1027 "params": {
1028 "service": "db",
1029 "method": "restore",
1030 "args": [
1031 "master-password",
1032 "base64-data-would-be-here",
1033 false,
1034 ]
1035 }
1036 });
1037 let actual = to_value(
1038 Restore {
1039 passwd: "master-password".into(),
1040 b64_data: "base64-data-would-be-here".into(),
1041 restore_type: RestoreType::Move,
1042 }
1043 .build(1000),
1044 )?;
1045
1046 assert_eq!(actual, expected);
1047
1048 Ok(())
1049 }
1050
1051 /// See [`crate::service::object::test::execute`] for more info
1052 #[test]
1053 fn restore_copy() -> Result<()> {
1054 let expected = json!({
1055 "jsonrpc": "2.0",
1056 "method": "call",
1057 "id": 1000,
1058 "params": {
1059 "service": "db",
1060 "method": "restore",
1061 "args": [
1062 "master-password",
1063 "base64-data-would-be-here",
1064 true,
1065 ]
1066 }
1067 });
1068 let actual = to_value(
1069 Restore {
1070 passwd: "master-password".into(),
1071 b64_data: "base64-data-would-be-here".into(),
1072 restore_type: RestoreType::Copy,
1073 }
1074 .build(1000),
1075 )?;
1076
1077 assert_eq!(actual, expected);
1078
1079 Ok(())
1080 }
1081
1082 /// See [`crate::service::object::test::execute_response`] for more info
1083 #[test]
1084 fn restore_response() -> Result<()> {
1085 let payload = json!({
1086 "jsonrpc": "2.0",
1087 "id": 1000,
1088 "result": true
1089 });
1090
1091 let response: JsonRpcResponse<RestoreResponse> = from_value(payload)?;
1092 match response {
1093 JsonRpcResponse::Error(e) => Err(e.error.into()),
1094 JsonRpcResponse::Success(_) => Ok(()),
1095 }
1096 }
1097
1098 /// See [`crate::service::object::test::execute`] for more info
1099 #[test]
1100 fn rename() -> Result<()> {
1101 let expected = json!({
1102 "jsonrpc": "2.0",
1103 "method": "call",
1104 "id": 1000,
1105 "params": {
1106 "service": "db",
1107 "method": "rename",
1108 "args": [
1109 "master-password",
1110 "old-database",
1111 "new-database"
1112 ]
1113 }
1114 });
1115 let actual = to_value(
1116 Rename {
1117 passwd: "master-password".into(),
1118 old_name: "old-database".into(),
1119 new_name: "new-database".into(),
1120 }
1121 .build(1000),
1122 )?;
1123
1124 assert_eq!(actual, expected);
1125
1126 Ok(())
1127 }
1128
1129 /// See [`crate::service::object::test::execute_response`] for more info
1130 #[test]
1131 fn rename_response() -> Result<()> {
1132 let payload = json!({
1133 "jsonrpc": "2.0",
1134 "id": 1000,
1135 "result": true
1136 });
1137
1138 let response: JsonRpcResponse<RenameResponse> = from_value(payload)?;
1139 match response {
1140 JsonRpcResponse::Error(e) => Err(e.error.into()),
1141 JsonRpcResponse::Success(_) => Ok(()),
1142 }
1143 }
1144
1145 /// See [`crate::service::object::test::execute`] for more info
1146 #[test]
1147 fn change_admin_password() -> Result<()> {
1148 let expected = json!({
1149 "jsonrpc": "2.0",
1150 "method": "call",
1151 "id": 1000,
1152 "params": {
1153 "service": "db",
1154 "method": "change_admin_password",
1155 "args": [
1156 "master-password",
1157 "new-master-password",
1158 ]
1159 }
1160 });
1161 let actual = to_value(
1162 ChangeAdminPassword {
1163 passwd: "master-password".into(),
1164 new_passwd: "new-master-password".into(),
1165 }
1166 .build(1000),
1167 )?;
1168
1169 assert_eq!(actual, expected);
1170
1171 Ok(())
1172 }
1173
1174 /// See [`crate::service::object::test::execute_response`] for more info
1175 #[test]
1176 fn change_admin_password_response() -> Result<()> {
1177 let payload = json!({
1178 "jsonrpc": "2.0",
1179 "id": 1000,
1180 "result": true
1181 });
1182
1183 let response: JsonRpcResponse<ChangeAdminPasswordResponse> = from_value(payload)?;
1184 match response {
1185 JsonRpcResponse::Error(e) => Err(e.error.into()),
1186 JsonRpcResponse::Success(_) => Ok(()),
1187 }
1188 }
1189
1190 /// See [`crate::service::object::test::execute`] for more info
1191 #[test]
1192 fn migrate_databases() -> Result<()> {
1193 let expected = json!({
1194 "jsonrpc": "2.0",
1195 "method": "call",
1196 "id": 1000,
1197 "params": {
1198 "service": "db",
1199 "method": "migrate_databases",
1200 "args": [
1201 "master-password",
1202 [
1203 "new-database",
1204 "new-database2",
1205 ]
1206 ]
1207 }
1208 });
1209 let actual = to_value(
1210 MigrateDatabases {
1211 passwd: "master-password".into(),
1212 databases: vec!["new-database".into(), "new-database2".into()],
1213 }
1214 .build(1000),
1215 )?;
1216
1217 assert_eq!(actual, expected);
1218
1219 Ok(())
1220 }
1221
1222 /// See [`crate::service::object::test::execute_response`] for more info
1223 #[test]
1224 fn migrate_databases_response() -> Result<()> {
1225 let payload = json!({
1226 "jsonrpc": "2.0",
1227 "id": 1000,
1228 "result": true
1229 });
1230
1231 let response: JsonRpcResponse<MigrateDatabasesResponse> = from_value(payload)?;
1232 match response {
1233 JsonRpcResponse::Error(e) => Err(e.error.into()),
1234 JsonRpcResponse::Success(_) => Ok(()),
1235 }
1236 }
1237
1238 /// See [`crate::service::object::test::execute`] for more info
1239 #[test]
1240 fn db_exist() -> Result<()> {
1241 let expected = json!({
1242 "jsonrpc": "2.0",
1243 "method": "call",
1244 "id": 1000,
1245 "params": {
1246 "service": "db",
1247 "method": "db_exist",
1248 "args": [
1249 "new-database"
1250 ]
1251 }
1252 });
1253 let actual = to_value(
1254 DbExist {
1255 db_name: "new-database".into(),
1256 }
1257 .build(1000),
1258 )?;
1259
1260 assert_eq!(actual, expected);
1261
1262 Ok(())
1263 }
1264
1265 /// See [`crate::service::object::test::execute_response`] for more info
1266 #[test]
1267 fn db_exist_response() -> Result<()> {
1268 let payload = json!({
1269 "jsonrpc": "2.0",
1270 "id": 1000,
1271 "result": true
1272 });
1273
1274 let response: JsonRpcResponse<DbExistResponse> = from_value(payload)?;
1275 match response {
1276 JsonRpcResponse::Error(e) => Err(e.error.into()),
1277 JsonRpcResponse::Success(_) => Ok(()),
1278 }
1279 }
1280
1281 /// See [`crate::service::object::test::execute`] for more info
1282 #[test]
1283 fn list() -> Result<()> {
1284 let expected = json!({
1285 "jsonrpc": "2.0",
1286 "method": "call",
1287 "id": 1000,
1288 "params": {
1289 "service": "db",
1290 "method": "list",
1291 "args": [
1292 false
1293 ]
1294 }
1295 });
1296 let actual = to_value(List { document: false }.build(1000))?;
1297
1298 assert_eq!(actual, expected);
1299
1300 Ok(())
1301 }
1302
1303 /// See [`crate::service::object::test::execute_response`] for more info
1304 #[test]
1305 fn list_response() -> Result<()> {
1306 let payload = json!({
1307 "jsonrpc": "2.0",
1308 "id": 1000,
1309 "result": [
1310 "old-database",
1311 "new-database",
1312 "new-database2"
1313 ]
1314 });
1315
1316 let response: JsonRpcResponse<ListResponse> = from_value(payload)?;
1317 match response {
1318 JsonRpcResponse::Error(e) => Err(e.error.into()),
1319 JsonRpcResponse::Success(_) => Ok(()),
1320 }
1321 }
1322
1323 /// See [`crate::service::object::test::execute`] for more info
1324 #[test]
1325 fn list_lang() -> Result<()> {
1326 let expected = json!({
1327 "jsonrpc": "2.0",
1328 "method": "call",
1329 "id": 1000,
1330 "params": {
1331 "service": "db",
1332 "method": "list_lang",
1333 "args": []
1334 }
1335 });
1336 let actual = to_value(ListLang {}.build(1000))?;
1337
1338 assert_eq!(actual, expected);
1339
1340 Ok(())
1341 }
1342
1343 /// See [`crate::service::object::test::execute_response`] for more info
1344 #[test]
1345 fn list_lang_response() -> Result<()> {
1346 let payload = json!({
1347 "jsonrpc": "2.0",
1348 "id": 1000,
1349 "result": [
1350 [
1351 "sq_AL",
1352 "Albanian / Shqip"
1353 ],
1354 [
1355 "am_ET",
1356 "Amharic / አምሃርኛ"
1357 ],
1358 [
1359 "ar_SY",
1360 "Arabic (Syria) / الْعَرَبيّة"
1361 ],
1362 // snipped for brevity
1363 ]
1364 });
1365
1366 let response: JsonRpcResponse<ListLangResponse> = from_value(payload)?;
1367 match response {
1368 JsonRpcResponse::Error(e) => Err(e.error.into()),
1369 JsonRpcResponse::Success(_) => Ok(()),
1370 }
1371 }
1372
1373 /// See [`crate::service::object::test::execute`] for more info
1374 #[test]
1375 fn list_countries() -> Result<()> {
1376 let expected = json!({
1377 "jsonrpc": "2.0",
1378 "method": "call",
1379 "id": 1000,
1380 "params": {
1381 "service": "db",
1382 "method": "list_countries",
1383 "args": [
1384 "master-password"
1385 ]
1386 }
1387 });
1388 let actual = to_value(
1389 ListCountries {
1390 passwd: "master-password".into(),
1391 }
1392 .build(1000),
1393 )?;
1394
1395 assert_eq!(actual, expected);
1396
1397 Ok(())
1398 }
1399
1400 /// See [`crate::service::object::test::execute_response`] for more info
1401 #[test]
1402 fn list_countries_response() -> Result<()> {
1403 let payload = json!({
1404 "jsonrpc": "2.0",
1405 "id": 1000,
1406 "result": [
1407 [
1408 "af",
1409 "Afghanistan"
1410 ],
1411 [
1412 "al",
1413 "Albania"
1414 ],
1415 [
1416 "dz",
1417 "Algeria"
1418 ],
1419 // snipped for brevity
1420 ]
1421 });
1422
1423 let response: JsonRpcResponse<ListCountriesResponse> = from_value(payload)?;
1424 match response {
1425 JsonRpcResponse::Error(e) => Err(e.error.into()),
1426 JsonRpcResponse::Success(_) => Ok(()),
1427 }
1428 }
1429
1430 /// See [`crate::service::object::test::execute`] for more info
1431 #[test]
1432 fn server_version() -> Result<()> {
1433 let expected = json!({
1434 "jsonrpc": "2.0",
1435 "method": "call",
1436 "id": 1000,
1437 "params": {
1438 "service": "db",
1439 "method": "server_version",
1440 "args": []
1441 }
1442 });
1443 let actual = to_value(ServerVersion {}.build(1000))?;
1444
1445 assert_eq!(actual, expected);
1446
1447 Ok(())
1448 }
1449
1450 /// See [`crate::service::object::test::execute_response`] for more info
1451 #[test]
1452 fn server_version_response() -> Result<()> {
1453 let payload = json!({
1454 "jsonrpc": "2.0",
1455 "id": 1000,
1456 "result": "14.0+e"
1457 });
1458
1459 let response: JsonRpcResponse<ServerVersionResponse> = from_value(payload)?;
1460 match response {
1461 JsonRpcResponse::Error(e) => Err(e.error.into()),
1462 JsonRpcResponse::Success(_) => Ok(()),
1463 }
1464 }
1465}