2
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
5
+ use crate :: collection:: DatastoreCollectionConfig ;
5
6
use crate :: schema:: { webhook_rx, webhook_rx_secret, webhook_rx_subscription} ;
6
7
use crate :: typed_uuid:: DbTypedUuid ;
8
+ use crate :: Generation ;
7
9
use chrono:: { DateTime , Utc } ;
8
10
use db_macros:: Resource ;
11
+ use omicron_common:: api:: external:: Error ;
9
12
use omicron_uuid_kinds:: { WebhookReceiverKind , WebhookReceiverUuid } ;
10
13
use serde:: { Deserialize , Serialize } ;
14
+ use std:: str:: FromStr ;
15
+ use uuid:: Uuid ;
11
16
12
17
/// A webhook receiver configuration.
13
18
#[ derive(
@@ -23,11 +28,31 @@ use serde::{Deserialize, Serialize};
23
28
#[ diesel( table_name = webhook_rx) ]
24
29
pub struct WebhookReceiver {
25
30
#[ diesel( embed) ]
26
- identity : WebhookReceiverIdentity ,
31
+ pub identity : WebhookReceiverIdentity ,
27
32
pub probes_enabled : bool ,
28
33
pub endpoint : String ,
34
+
35
+ /// child resource generation number, per RFD 192
36
+ pub rcgen : Generation ,
37
+ }
38
+
39
+ impl DatastoreCollectionConfig < WebhookRxSecret > for WebhookReceiver {
40
+ type CollectionId = Uuid ;
41
+ type GenerationNumberColumn = webhook_rx:: dsl:: rcgen ;
42
+ type CollectionTimeDeletedColumn = webhook_rx:: dsl:: time_deleted ;
43
+ type CollectionIdColumn = webhook_rx_secret:: dsl:: rx_id ;
44
+ }
45
+
46
+ impl DatastoreCollectionConfig < WebhookRxSubscription > for WebhookReceiver {
47
+ type CollectionId = Uuid ;
48
+ type GenerationNumberColumn = webhook_rx:: dsl:: rcgen ;
49
+ type CollectionTimeDeletedColumn = webhook_rx:: dsl:: time_deleted ;
50
+ type CollectionIdColumn = webhook_rx_subscription:: dsl:: rx_id ;
29
51
}
30
52
53
+ // TODO(eliza): should deliveries/delivery attempts also be treated as children
54
+ // of a webhook receiver?
55
+
31
56
#[ derive(
32
57
Clone , Debug , Queryable , Selectable , Insertable , Serialize , Deserialize ,
33
58
) ]
@@ -46,13 +71,23 @@ pub struct WebhookRxSecret {
46
71
#[ diesel( table_name = webhook_rx_subscription) ]
47
72
pub struct WebhookRxSubscription {
48
73
pub rx_id : DbTypedUuid < WebhookReceiverKind > ,
74
+ #[ diesel( embed) ]
75
+ pub glob : WebhookGlob ,
76
+ pub time_created : DateTime < Utc > ,
77
+ }
78
+
79
+ #[ derive(
80
+ Clone , Debug , Queryable , Selectable , Insertable , Serialize , Deserialize ,
81
+ ) ]
82
+ #[ diesel( table_name = webhook_rx_subscription) ]
83
+ pub struct WebhookGlob {
49
84
pub event_class : String ,
50
85
pub similar_to : String ,
51
- pub time_created : DateTime < Utc > ,
52
86
}
53
87
54
- impl WebhookRxSubscription {
55
- pub fn new ( rx_id : WebhookReceiverUuid , event_class : String ) -> Self {
88
+ impl FromStr for WebhookGlob {
89
+ type Err = Error ;
90
+ fn from_str ( event_class : & str ) -> Result < Self , Self :: Err > {
56
91
fn seg2regex ( segment : & str , similar_to : & mut String ) {
57
92
match segment {
58
93
// Match one segment (i.e. any number of segment characters)
@@ -63,6 +98,7 @@ impl WebhookRxSubscription {
63
98
// Because `_` his a metacharacter in Postgres' SIMILAR TO
64
99
// regexes, we've gotta go through and escape them.
65
100
s => {
101
+ // TODO(eliza): validate what characters are in the segment...
66
102
for s in s. split_inclusive ( '_' ) {
67
103
// Handle the fact that there might not be a `_` in the
68
104
// string at all
@@ -87,19 +123,19 @@ impl WebhookRxSubscription {
87
123
seg2regex ( segment, & mut similar_to) ;
88
124
}
89
125
} else {
90
- // TODO(eliza): we should probably validate that the event class has
91
- // at least one segment...
126
+ return Err ( Error :: invalid_value (
127
+ "event_class" ,
128
+ "must not be empty" ,
129
+ ) ) ;
92
130
} ;
93
131
94
- // `_` is a metacharacter in Postgres' SIMILAR TO regexes, so escape
95
- // them.
132
+ Ok ( Self { event_class : event_class. to_string ( ) , similar_to } )
133
+ }
134
+ }
96
135
97
- Self {
98
- rx_id : DbTypedUuid ( rx_id) ,
99
- event_class,
100
- similar_to,
101
- time_created : Utc :: now ( ) ,
102
- }
136
+ impl WebhookRxSubscription {
137
+ pub fn new ( rx_id : WebhookReceiverUuid , glob : WebhookGlob ) -> Self {
138
+ Self { rx_id : DbTypedUuid ( rx_id) , glob, time_created : Utc :: now ( ) }
103
139
}
104
140
}
105
141
@@ -119,13 +155,17 @@ mod test {
119
155
( "foo_bar.baz" , "foo\\ _bar.baz" ) ,
120
156
( "foo_bar.*.baz" , "foo\\ _bar.[a-zA-Z0-9\\ _\\ -]+.baz" ) ,
121
157
] ;
122
- let rx_id = WebhookReceiverUuid :: new_v4 ( ) ;
123
158
for ( class, regex) in CASES {
124
- let subscription =
125
- WebhookRxSubscription :: new ( rx_id, dbg ! ( class) . to_string ( ) ) ;
159
+ let glob = match WebhookGlob :: from_str ( dbg ! ( class) ) {
160
+ Ok ( glob) => glob,
161
+ Err ( error) => panic ! (
162
+ "event class glob {class:?} should produce the regex
163
+ {regex:?}, but instead failed to parse: {error}"
164
+ ) ,
165
+ } ;
126
166
assert_eq ! (
127
167
dbg!( regex) ,
128
- dbg!( & subscription . similar_to) ,
168
+ dbg!( & glob . similar_to) ,
129
169
"event class {class:?} should produce the regex {regex:?}"
130
170
) ;
131
171
}
0 commit comments