///|
/// Built-in provider families supported by MoonHook adapters.
pub(all) enum HookProvider {
GitHub
GitLab
Gitee
Feishu
WeCom
Stripe
Custom(String)
} derive(Eq, Debug)
///|
/// A normalized incoming WebHook request.
pub(all) struct HookRequest {
provider : HookProvider
event_type : String
delivery_id : String
timestamp : String?
headers : Map[String, String]
body : String
} derive(Eq, Debug)
///|
/// A normalized event after provider-specific parsing.
pub(all) struct HookEvent {
provider : HookProvider
event_type : String
delivery_id : String
idempotency_key : String
body : String
attributes : Map[String, String]
} derive(Eq, Debug)
///|
/// Create a request with normalized names used by all adapters.
pub fn HookRequest::HookRequest(
provider : HookProvider,
event_type : StringView,
delivery_id : StringView,
body : StringView,
timestamp? : StringView,
headers? : Map[String, String] = Map([]),
) -> HookRequest {
{
provider,
event_type: event_type.to_owned(),
delivery_id: delivery_id.to_owned(),
timestamp: timestamp.map(v => v.to_owned()),
headers,
body: body.to_owned(),
}
}
///|
/// Convert a raw request into the minimal normalized event.
pub fn HookRequest::to_event(self : HookRequest) -> HookEvent {
{
provider: self.provider,
event_type: self.event_type,
delivery_id: self.delivery_id,
idempotency_key: make_idempotency_key(self.provider, self.delivery_id),
body: self.body,
attributes: Map([]),
}
}
///|
/// Return a stable provider slug for logs, ids, and JSON fields.
pub fn HookProvider::slug(self : HookProvider) -> String {
match self {
GitHub => "github"
GitLab => "gitlab"
Gitee => "gitee"
Feishu => "feishu"
WeCom => "wecom"
Stripe => "stripe"
Custom(name) => name
}
}
///|
/// Build the default idempotency key from provider and delivery id.
pub fn make_idempotency_key(
provider : HookProvider,
delivery_id : String,
) -> String {
provider.slug() + ":" + delivery_id
}
///|
/// Return a header by normalized, case-insensitive key.
pub fn HookRequest::header(self : HookRequest, name : StringView) -> String? {
let normalized = name.to_owned().to_lower()
match self.headers.get(normalized) {
Some(value) => Some(value)
None => {
for key, value in self.headers {
if key.to_lower() == normalized {
return Some(value)
}
}
None
}
}
}
///|
/// Read a header from a raw map using HTTP's case-insensitive name rules.
pub fn header_value(
headers : Map[String, String],
name : StringView,
) -> String? {
let normalized = name.to_owned().to_lower()
match headers.get(normalized) {
Some(value) => Some(value)
None => {
for key, value in headers {
if key.to_lower() == normalized {
return Some(value)
}
}
None
}
}
}
///|
/// Check whether the request carries an event type.
pub fn HookRequest::has_event_type(
self : HookRequest,
event_type : StringView,
) -> Bool {
self.event_type == event_type.to_owned()
}
///|
/// Check whether the request comes from a provider.
pub fn HookRequest::is_from(
self : HookRequest,
provider : HookProvider,
) -> Bool {
self.provider == provider
}
///|
/// Check whether the event matches the given provider.
pub fn HookEvent::is_from(self : HookEvent, provider : HookProvider) -> Bool {
self.provider == provider
}
///|
/// Check whether the event has a specific normalized type.
pub fn HookEvent::is_type(self : HookEvent, event_type : StringView) -> Bool {
self.event_type == event_type.to_owned()
}
///|
/// Attach a single parsed attribute to an event.
pub fn HookEvent::with_attribute(
self : HookEvent,
key : StringView,
value : StringView,
) -> HookEvent {
let attrs = self.attributes
attrs[key.to_owned()] = value.to_owned()
{
provider: self.provider,
event_type: self.event_type,
delivery_id: self.delivery_id,
idempotency_key: self.idempotency_key,
body: self.body,
attributes: attrs,
}
}
///|
/// Replace normalized event identity and rebuild the idempotency key.
pub fn HookEvent::with_identity(
self : HookEvent,
event_type? : String = self.event_type,
delivery_id? : String = self.delivery_id,
) -> HookEvent {
{
provider: self.provider,
event_type,
delivery_id,
idempotency_key: make_idempotency_key(self.provider, delivery_id),
body: self.body,
attributes: self.attributes,
}
}
///|
/// Read an optional parsed attribute.
pub fn HookEvent::attribute(self : HookEvent, key : StringView) -> String? {
self.attributes.get(key.to_owned())
}