Skip to content

Commit

Permalink
Merge pull request #12 from d--j/log-message
Browse files Browse the repository at this point in the history
add String() method to Response for logging purposes
  • Loading branch information
d--j authored Sep 14, 2023
2 parents 115badd + 3401e24 commit f4e3ac5
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 4 deletions.
75 changes: 71 additions & 4 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,89 @@ type Response struct {
}

// Response returns message instance with data
func (c *Response) Response() *wire.Message {
return &wire.Message{Code: c.code, Data: c.data}
func (r *Response) Response() *wire.Message {
return &wire.Message{Code: r.code, Data: r.data}
}

// Continue returns false if the MTA should stop sending events for this transaction, true otherwise.
// A [RespDiscard] Response will return false because the MTA should end sending events for the current
// SMTP transaction to this milter.
func (c *Response) Continue() bool {
switch wire.ActionCode(c.code) {
func (r *Response) Continue() bool {
switch wire.ActionCode(r.code) {
case wire.ActAccept, wire.ActDiscard, wire.ActReject, wire.ActTempFail, wire.ActReplyCode:
return false
default:
return true
}
}

// String returns a string representation of this response.
// Can be used for logging purposes.
// This method will always return a logfmt compatible string.
// We try to not alter the output of this method arbitrarily – but we do not make any guaranties.
//
// It sometimes internally examines the bytes that will be sent over the wire with the parsing code
// of the client part of this library. This is not the most performant implementation, so
// you might opt to not use this method when your code needs to be performant.
func (r *Response) String() string {
switch wire.ActionCode(r.code) {
case wire.ActContinue:
return "response=continue"
case wire.ActAccept:
return "response=accept"
case wire.ActDiscard:
return "response=discard"
case wire.ActReject:
return "response=reject"
case wire.ActTempFail:
return "response=temp_fail"
case wire.ActSkip:
return "response=skip"
case wire.ActProgress:
return "response=progress"
case wire.ActReplyCode:
act, err := parseAction(r.Response())
if err != nil {
return fmt.Sprintf("response=invalid code=%d data_len=%d data=%q", r.code, len(r.data), r.data)
}
action := "temp_fail"
if act.SMTPCode > 499 {
action = "reject"
}
return fmt.Sprintf("response=reply_code action=%s code=%d reason=%q", action, act.SMTPCode, act.SMTPReply)
}
// Users of the library do not really see modification Response objects.
// This is just for completeness’ sake
act, err := parseModifyAct(r.Response())
if err == nil {
switch act.Type {
case ActionAddRcpt:
if act.RcptArgs != "" {
return fmt.Sprintf("response=add_rcpt rcpt=%q args=%q", act.Rcpt, act.RcptArgs)
}
return fmt.Sprintf("response=add_rcpt rcpt=%q", act.Rcpt)
case ActionDelRcpt:
return fmt.Sprintf("response=del_rcpt rcpt=%q", act.Rcpt)
case ActionQuarantine:
return fmt.Sprintf("response=quarantine reason=%q", act.Reason)
case ActionReplaceBody:
return fmt.Sprintf("response=replace_body len=%d", len(act.Body))
case ActionChangeFrom:
if act.FromArgs != "" {
return fmt.Sprintf("response=change_from from=%q args=%q", act.From, act.FromArgs)
}
return fmt.Sprintf("response=change_from from=%q", act.From)
case ActionAddHeader:
return fmt.Sprintf("response=add_header name=%q value=%q", act.HeaderName, act.HeaderValue)
case ActionChangeHeader:
return fmt.Sprintf("response=change_header name=%q value=%q index=%d", act.HeaderName, act.HeaderValue, act.HeaderIndex)
case ActionInsertHeader:
return fmt.Sprintf("response=insert_header name=%q value=%q index=%d", act.HeaderName, act.HeaderValue, act.HeaderIndex)
}
}
return fmt.Sprintf("response=unknown code=%d data_len=%d data=%q", r.code, len(r.data), r.data)
}

// newResponse generates a new Response suitable for [wire.WritePacket]
func newResponse(code wire.Code, data []byte) *Response {
return &Response{code, data}
Expand Down
48 changes: 48 additions & 0 deletions response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,51 @@ func TestCustomResponseDefaultResponse(t *testing.T) {
})
}
}

func TestResponse_String(t *testing.T) {
type fields struct {
code wire.Code
data []byte
}
tests := []struct {
name string
fields fields
want string
}{
{"continue", fields{wire.Code(wire.ActContinue), nil}, "response=continue"},
{"accept", fields{wire.Code(wire.ActAccept), nil}, "response=accept"},
{"discard", fields{wire.Code(wire.ActDiscard), nil}, "response=discard"},
{"reject", fields{wire.Code(wire.ActReject), nil}, "response=reject"},
{"temp_fail", fields{wire.Code(wire.ActTempFail), nil}, "response=temp_fail"},
{"skip", fields{wire.Code(wire.ActSkip), nil}, "response=skip"},
{"progress", fields{wire.Code(wire.ActProgress), nil}, "response=progress"},
{"reply_code1", fields{wire.Code(wire.ActReplyCode), []byte("444 test\x00")}, "response=reply_code action=temp_fail code=444 reason=\"444 test\""},
{"reply_code2", fields{wire.Code(wire.ActReplyCode), []byte("555 test\x00")}, "response=reply_code action=reject code=555 reason=\"555 test\""},
{"reply_code3", fields{wire.Code(wire.ActReplyCode), []byte("continue\x00")}, "response=invalid code=121 data_len=9 data=\"continue\\x00\""},
{"add_rcpt1", fields{wire.Code(wire.ActAddRcpt), []byte("<>\x00")}, "response=add_rcpt rcpt=\"<>\""},
{"add_rcpt2", fields{wire.Code(wire.ActAddRcptPar), []byte("<>\x00A=B\x00")}, "response=add_rcpt rcpt=\"<>\" args=\"A=B\""},
{"del_rcpt", fields{wire.Code(wire.ActDelRcpt), []byte("<>\x00A=B\x00")}, "response=del_rcpt rcpt=\"<>\""},
{"quarantine", fields{wire.Code(wire.ActQuarantine), []byte("spam\x00")}, "response=quarantine reason=\"spam\""},
{"replace_body", fields{wire.Code(wire.ActReplBody), []byte("1234")}, "response=replace_body len=4"},
{"change_from1", fields{wire.Code(wire.ActChangeFrom), []byte("<>\x00")}, "response=change_from from=\"<>\""},
{"change_from2", fields{wire.Code(wire.ActChangeFrom), []byte("<>\x00A=B\x00")}, "response=change_from from=\"<>\" args=\"A=B\""},
{"add_header", fields{wire.Code(wire.ActAddHeader), []byte("X-Test\x00Test\x00")}, "response=add_header name=\"X-Test\" value=\"Test\""},
{"change_header", fields{wire.Code(wire.ActChangeHeader), []byte("\x00\x00\x00\x01X-Test\x00Test\x00")}, "response=change_header name=\"X-Test\" value=\"Test\" index=1"},
{"insert_header", fields{wire.Code(wire.ActInsertHeader), []byte("\x00\x00\x00\x01X-Test\x00Test\x00")}, "response=insert_header name=\"X-Test\" value=\"Test\" index=1"},
{"garbage", fields{wire.Code(0), []byte("\x00\x00\x00\x00")}, "response=unknown code=0 data_len=4 data=\"\\x00\\x00\\x00\\x00\""},
{"garbage-nil", fields{wire.Code(128), nil}, "response=unknown code=128 data_len=0 data=\"\""},
}
for _, tt_ := range tests {
t.Run(tt_.name, func(t *testing.T) {
tt := tt_
t.Parallel()
r := &Response{
code: tt.fields.code,
data: tt.fields.data,
}
if got := r.String(); got != tt.want {
t.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit f4e3ac5

Please sign in to comment.