improve webhook parsing
This commit is contained in:
+100
-161
@@ -1,46 +1,43 @@
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::errors::AppError;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum WebhookType {
|
||||
Review(u64, String),
|
||||
Review(ReviewPayload),
|
||||
}
|
||||
|
||||
impl TryFrom<Value> for WebhookType {
|
||||
type Error = AppError;
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ReviewPayload {
|
||||
pub action: String,
|
||||
pub pull_request: PullRequest,
|
||||
pub comment: Comment,
|
||||
}
|
||||
|
||||
fn try_from(json: Value) -> Result<Self, Self::Error> {
|
||||
let pull_request = json.get("pull_request");
|
||||
let comment = json.get("comment");
|
||||
let action = json
|
||||
.get("action")
|
||||
.ok_or(AppError::MissingField("action".into()))?
|
||||
.as_str()
|
||||
.ok_or(AppError::WrongFieldType("action".into()))?;
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct PullRequest {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
if action != "created" {
|
||||
return Err(AppError::BadJsonStructErr);
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Comment {
|
||||
pub id: u64,
|
||||
pub body: String,
|
||||
pub user: User,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct User {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
impl WebhookType {
|
||||
pub fn from_event(event: &str, json: Value) -> Result<Self, AppError> {
|
||||
match event {
|
||||
"pull_request_comment" => Ok(WebhookType::Review(serde_json::from_value(json)?)),
|
||||
_ => Err(AppError::UnknownEventErr),
|
||||
}
|
||||
|
||||
if let (Some(pull_request), Some(comment)) = (pull_request, comment) {
|
||||
let comment_body = comment
|
||||
.get("body")
|
||||
.ok_or(AppError::MissingField("comment.body".into()))?
|
||||
.as_str()
|
||||
.ok_or(AppError::WrongFieldType("comment.body".into()))?
|
||||
.to_string();
|
||||
|
||||
let pr_id = pull_request
|
||||
.get("id")
|
||||
.ok_or(AppError::MissingField("pull_request.id".into()))?
|
||||
.as_u64()
|
||||
.ok_or(AppError::WrongFieldType("pull_request.id".into()))?;
|
||||
|
||||
return Ok(WebhookType::Review(pr_id, comment_body));
|
||||
}
|
||||
|
||||
Err(AppError::BadJsonStructErr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,149 +47,91 @@ mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn valid_webhook_parses_review() {
|
||||
let payload = json!({
|
||||
fn test_from_event_valid_pull_request_comment() {
|
||||
let json = json!({
|
||||
"action": "created",
|
||||
"pull_request": { "id": 42 },
|
||||
"comment": { "body": "LGTM" }
|
||||
"pull_request": {
|
||||
"id": 42
|
||||
},
|
||||
"comment": {
|
||||
"id": 7,
|
||||
"body": "LGTM",
|
||||
"user": {
|
||||
"id": 100
|
||||
}
|
||||
}
|
||||
});
|
||||
let result = WebhookType::try_from(payload).unwrap();
|
||||
assert_eq!(result, WebhookType::Review(42, "LGTM".into()));
|
||||
|
||||
let result = WebhookType::from_event("pull_request_comment", json);
|
||||
assert!(result.is_ok());
|
||||
|
||||
match result.unwrap() {
|
||||
WebhookType::Review(payload) => {
|
||||
assert_eq!(payload.action, "created");
|
||||
assert_eq!(payload.pull_request.id, 42);
|
||||
assert_eq!(payload.comment.id, 7);
|
||||
assert_eq!(payload.comment.body, "LGTM");
|
||||
assert_eq!(payload.comment.user.id, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_action_returns_error() {
|
||||
let payload = json!({
|
||||
"pull_request": { "id": 1 },
|
||||
"comment": { "body": "ok" }
|
||||
});
|
||||
let err = WebhookType::try_from(payload).unwrap_err();
|
||||
assert!(matches!(err, AppError::MissingField(ref f) if f == "action"));
|
||||
fn test_from_event_unknown_event() {
|
||||
let json = json!({});
|
||||
let result = WebhookType::from_event("push", json);
|
||||
assert!(result.is_err());
|
||||
|
||||
match result.unwrap_err() {
|
||||
AppError::UnknownEventErr => {}
|
||||
_ => panic!("expected UnknownEventErr"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn action_not_created_returns_bad_json_struct() {
|
||||
let payload = json!({
|
||||
"action": "updated",
|
||||
"pull_request": { "id": 1 },
|
||||
"comment": { "body": "ok" }
|
||||
fn test_from_event_malformed_json() {
|
||||
let json = json!({
|
||||
"action": "created"
|
||||
// pull_request and comment are missing
|
||||
});
|
||||
let err = WebhookType::try_from(payload).unwrap_err();
|
||||
assert!(matches!(err, AppError::BadJsonStructErr));
|
||||
|
||||
let result = WebhookType::from_event("pull_request_comment", json);
|
||||
assert!(result.is_err());
|
||||
|
||||
match result.unwrap_err() {
|
||||
AppError::BadJsonStructErr(_) => {}
|
||||
_ => panic!("expected BadJsonStructErr"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn action_not_a_string_returns_error() {
|
||||
let payload = json!({
|
||||
"action": 123,
|
||||
"pull_request": { "id": 1 },
|
||||
"comment": { "body": "ok" }
|
||||
fn test_deserialize_review_payload() {
|
||||
let json = json!({
|
||||
"action": "edited",
|
||||
"pull_request": {
|
||||
"id": 99
|
||||
},
|
||||
"comment": {
|
||||
"id": 12,
|
||||
"body": "Needs work",
|
||||
"user": {
|
||||
"id": 200
|
||||
}
|
||||
}
|
||||
});
|
||||
let err = WebhookType::try_from(payload).unwrap_err();
|
||||
assert!(matches!(err, AppError::WrongFieldType(ref f) if f == "action"));
|
||||
|
||||
let payload: ReviewPayload = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(payload.action, "edited");
|
||||
assert_eq!(payload.pull_request.id, 99);
|
||||
assert_eq!(payload.comment.id, 12);
|
||||
assert_eq!(payload.comment.body, "Needs work");
|
||||
assert_eq!(payload.comment.user.id, 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_pull_request_returns_bad_json_struct() {
|
||||
let payload = json!({
|
||||
"action": "created",
|
||||
"comment": { "body": "ok" }
|
||||
});
|
||||
let err = WebhookType::try_from(payload).unwrap_err();
|
||||
assert!(matches!(err, AppError::BadJsonStructErr));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_comment_returns_bad_json_struct() {
|
||||
let payload = json!({
|
||||
"action": "created",
|
||||
"pull_request": { "id": 1 }
|
||||
});
|
||||
let err = WebhookType::try_from(payload).unwrap_err();
|
||||
assert!(matches!(err, AppError::BadJsonStructErr));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_pr_id_returns_error() {
|
||||
let payload = json!({
|
||||
"action": "created",
|
||||
"pull_request": { "number": 1 },
|
||||
"comment": { "body": "ok" }
|
||||
});
|
||||
let err = WebhookType::try_from(payload).unwrap_err();
|
||||
assert!(matches!(err, AppError::MissingField(ref f) if f == "pull_request.id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pr_id_not_a_number_returns_error() {
|
||||
let payload = json!({
|
||||
"action": "created",
|
||||
"pull_request": { "id": "not-a-number" },
|
||||
"comment": { "body": "ok" }
|
||||
});
|
||||
let err = WebhookType::try_from(payload).unwrap_err();
|
||||
assert!(matches!(err, AppError::WrongFieldType(ref f) if f == "pull_request.id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_comment_body_returns_error() {
|
||||
let payload = json!({
|
||||
"action": "created",
|
||||
"pull_request": { "id": 1 },
|
||||
"comment": { "text": "no body" }
|
||||
});
|
||||
let err = WebhookType::try_from(payload).unwrap_err();
|
||||
assert!(matches!(err, AppError::MissingField(ref f) if f == "comment.body"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_body_not_a_string_returns_error() {
|
||||
let payload = json!({
|
||||
"action": "created",
|
||||
"pull_request": { "id": 1 },
|
||||
"comment": { "body": 999 }
|
||||
});
|
||||
let err = WebhookType::try_from(payload).unwrap_err();
|
||||
assert!(matches!(err, AppError::WrongFieldType(ref f) if f == "comment.body"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null_pull_request_returns_error() {
|
||||
let payload = json!({
|
||||
"action": "created",
|
||||
"pull_request": null,
|
||||
"comment": { "body": "ok" }
|
||||
});
|
||||
let err = WebhookType::try_from(payload).unwrap_err();
|
||||
assert!(matches!(err, AppError::MissingField(ref f) if f == "pull_request.id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn null_comment_returns_error() {
|
||||
let payload = json!({
|
||||
"action": "created",
|
||||
"pull_request": { "id": 1 },
|
||||
"comment": null
|
||||
});
|
||||
let err = WebhookType::try_from(payload).unwrap_err();
|
||||
assert!(matches!(err, AppError::MissingField(ref f) if f == "comment.body"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_pr_id_parses_correctly() {
|
||||
let payload = json!({
|
||||
"action": "created",
|
||||
"pull_request": { "id": 18446744073709551615u64 },
|
||||
"comment": { "body": "max u64" }
|
||||
});
|
||||
let result = WebhookType::try_from(payload).unwrap();
|
||||
assert_eq!(result, WebhookType::Review(18446744073709551615, "max u64".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_webhook_payload_parses() {
|
||||
let payload: Value = serde_json::from_str(include_str!("../docs/webhook_pr_body.json")).unwrap();
|
||||
let result = WebhookType::try_from(payload).unwrap();
|
||||
assert_eq!(result, WebhookType::Review(1, "Test comment".into()));
|
||||
fn test_from_event_empty_json() {
|
||||
let result = WebhookType::from_event("pull_request_comment", json!({}));
|
||||
assert!(result.is_err());
|
||||
assert!(matches!(result.unwrap_err(), AppError::BadJsonStructErr(_)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user