1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! SQLx integration for BO4E identifier types.
//!
//! ## What is implemented
//!
//! When the `sqlx` feature is enabled the following traits are implemented for
//! **every** identifier type in [`crate::identifiers`]:
//!
//! | Trait | Effect |
//! |-------|--------|
//! | `sqlx::Type<Postgres>` | maps to PostgreSQL `TEXT` |
//! | `sqlx::Encode<'_, Postgres>` | binds as `&str` (zero-copy) |
//! | `sqlx::Decode<'_, Postgres>` | reads `TEXT`, validates, returns typed ID |
//! | `sqlx::postgres::PgHasArrayType` | maps `Vec<Id>` to a `TEXT[]` column |
//!
//! ## Usage
//!
//! Use the identifier type directly as a bind parameter or result column:
//!
//! ```no_run
//! use rubo4e::identifiers::{MaloId, MarktpartnerId};
//! use sqlx::Row as _;
//!
//! # async fn demo(pool: sqlx::PgPool, malo_id: MaloId) -> Result<(), sqlx::Error> {
//! // As a query bind parameter:
//! sqlx::query("INSERT INTO malo (id) VALUES ($1)")
//! .bind(&malo_id) // MaloId implements Encode
//! .execute(&pool).await?;
//!
//! // As a result column via try_get:
//! let row = sqlx::query("SELECT malo_id, mp_id FROM parties LIMIT 1")
//! .fetch_one(&pool).await?;
//! let id: MaloId = row.try_get("malo_id")?;
//! let mp: MarktpartnerId = row.try_get("mp_id")?;
//!
//! // As a struct field with FromRow:
//! #[derive(sqlx::FromRow)]
//! struct MpRow {
//! mp_id: MarktpartnerId, // decoded + validated automatically
//! }
//! let rows: Vec<MpRow> = sqlx::query_as("SELECT mp_id FROM parties")
//! .fetch_all(&pool).await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## Error behaviour
//!
//! Decoding validates the value using the same rules as `TryFrom<String>`.
//! An invalid value stored in the database (e.g. a MaLo-ID with a wrong check
//! digit) causes `row.try_get(...)` to return `Err(...)` wrapping an
//! [`IdentifierError`](crate::error::IdentifierError).
//!
//! ## Array columns
//!
//! `PgHasArrayType` is implemented for every identifier, so `Vec<MaloId>` binds
//! to a `TEXT[]` column directly:
//!
//! ```no_run
//! use rubo4e::identifiers::MaloId;
//!
//! # async fn demo(pool: sqlx::PgPool, ids: Vec<MaloId>) -> Result<(), sqlx::Error> {
//! sqlx::query("SELECT * FROM malo WHERE id = ANY($1)")
//! .bind(&ids)
//! .fetch_all(&pool).await?;
//! # Ok(())
//! # }
//! ```
//!
//! This has to live here rather than in downstream code: both `PgHasArrayType`
//! and the identifier types are foreign to any consuming crate, so the orphan
//! rule makes a local impl impossible.
/// Stamps out `sqlx::Type + Encode + Decode` for a newtype that wraps a
/// validated string and implements `TryFrom<String>` + `AsRef<str>`.
use crate;
impl_sqlx_text!;