@@ -6,7 +6,7 @@ use crate::schema::{webhook_rx, webhook_rx_secret, webhook_rx_subscription};
6
6
use crate :: typed_uuid:: DbTypedUuid ;
7
7
use chrono:: { DateTime , Utc } ;
8
8
use db_macros:: Resource ;
9
- use omicron_uuid_kinds:: WebhookReceiverKind ;
9
+ use omicron_uuid_kinds:: { WebhookReceiverKind , WebhookReceiverUuid } ;
10
10
use serde:: { Deserialize , Serialize } ;
11
11
12
12
/// A webhook receiver configuration.
@@ -47,5 +47,87 @@ pub struct WebhookRxSecret {
47
47
pub struct WebhookRxSubscription {
48
48
pub rx_id : DbTypedUuid < WebhookReceiverKind > ,
49
49
pub event_class : String ,
50
+ pub similar_to : String ,
50
51
pub time_created : DateTime < Utc > ,
51
52
}
53
+
54
+ impl WebhookRxSubscription {
55
+ pub fn new ( rx_id : WebhookReceiverUuid , event_class : String ) -> Self {
56
+ fn seg2regex ( segment : & str , similar_to : & mut String ) {
57
+ match segment {
58
+ // Match one segment (i.e. any number of segment characters)
59
+ "*" => similar_to. push_str ( "[a-zA-Z0-9\\ _\\ -]+" ) ,
60
+ // Match any number of segments
61
+ "**" => similar_to. push ( '%' ) ,
62
+ // Match the literal segment.
63
+ // Because `_` his a metacharacter in Postgres' SIMILAR TO
64
+ // regexes, we've gotta go through and escape them.
65
+ s => {
66
+ for s in s. split_inclusive ( '_' ) {
67
+ // Handle the fact that there might not be a `_` in the
68
+ // string at all
69
+ if let Some ( s) = s. strip_suffix ( '_' ) {
70
+ similar_to. push_str ( s) ;
71
+ similar_to. push_str ( "\\ _" ) ;
72
+ } else {
73
+ similar_to. push_str ( s) ;
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ // The subscription's regex will always be at least as long as the event class.
81
+ let mut similar_to = String :: with_capacity ( event_class. len ( ) ) ;
82
+ let mut segments = event_class. split ( '.' ) ;
83
+ if let Some ( segment) = segments. next ( ) {
84
+ seg2regex ( segment, & mut similar_to) ;
85
+ for segment in segments {
86
+ similar_to. push ( '.' ) ; // segment separator
87
+ seg2regex ( segment, & mut similar_to) ;
88
+ }
89
+ } else {
90
+ // TODO(eliza): we should probably validate that the event class has
91
+ // at least one segment...
92
+ } ;
93
+
94
+ // `_` is a metacharacter in Postgres' SIMILAR TO regexes, so escape
95
+ // them.
96
+
97
+ Self {
98
+ rx_id : DbTypedUuid ( rx_id) ,
99
+ event_class,
100
+ similar_to,
101
+ time_created : Utc :: now ( ) ,
102
+ }
103
+ }
104
+ }
105
+
106
+ #[ cfg( test) ]
107
+ mod test {
108
+ use super :: * ;
109
+
110
+ #[ test]
111
+ fn test_event_class_glob_to_regex ( ) {
112
+ const CASES : & [ ( & str , & str ) ] = & [
113
+ ( "foo.bar" , "foo.bar" ) ,
114
+ ( "foo.*.bar" , "foo.[a-zA-Z0-9\\ _\\ -]+.bar" ) ,
115
+ ( "foo.*" , "foo.[a-zA-Z0-9\\ _\\ -]+" ) ,
116
+ ( "*.foo" , "[a-zA-Z0-9\\ _\\ -]+.foo" ) ,
117
+ ( "foo.**.bar" , "foo.%.bar" ) ,
118
+ ( "foo.**" , "foo.%" ) ,
119
+ ( "foo_bar.baz" , "foo\\ _bar.baz" ) ,
120
+ ( "foo_bar.*.baz" , "foo\\ _bar.[a-zA-Z0-9\\ _\\ -]+.baz" ) ,
121
+ ] ;
122
+ let rx_id = WebhookReceiverUuid :: new_v4 ( ) ;
123
+ for ( class, regex) in CASES {
124
+ let subscription =
125
+ WebhookRxSubscription :: new ( rx_id, dbg ! ( class) . to_string ( ) ) ;
126
+ assert_eq ! (
127
+ dbg!( regex) ,
128
+ dbg!( & subscription. similar_to) ,
129
+ "event class {class:?} should produce the regex {regex:?}"
130
+ ) ;
131
+ }
132
+ }
133
+ }
0 commit comments