-
Notifications
You must be signed in to change notification settings - Fork 0
/
output.go
154 lines (129 loc) · 3.35 KB
/
output.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/*
Copyright 2019 Dmitry Kolesnikov, All Rights Reserved
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gouldian
import (
"encoding/json"
"fmt"
"net/http"
"net/textproto"
"sync"
"github.com/fogfish/guid/v2"
)
// Global pools
var (
outputs sync.Pool
)
func init() {
outputs.New = func() interface{} {
return &Output{
Headers: make([]struct {
Header string
Value string
}, 0, 20),
}
}
}
// Output
type Output struct {
Status int
Headers []struct{ Header, Value string }
Body string
Failure error
}
// Output uses "error" interface
func (out Output) Error() string {
return out.Body
}
// NewOutput creates HTTP response with given HTTP Status code
func NewOutput(status int) *Output {
out := outputs.Get().(*Output)
out.Status = status
return out
}
// Free releases output
func (out *Output) Free() {
out.Failure = nil
out.Body = ""
out.Headers = out.Headers[:0]
outputs.Put(out)
}
func (out *Output) SetHeader(header, value string) {
out.Headers = append(out.Headers,
struct {
Header string
Value string
}{textproto.CanonicalMIMEHeaderKey(header), value},
)
}
func (out *Output) GetHeader(header string) string {
h := textproto.CanonicalMIMEHeaderKey(header)
for i := 0; i < len(out.Headers); i++ {
if out.Headers[i].Header == h {
return out.Headers[i].Value
}
}
return ""
}
// WithIssue appends Issue, RFC 7807: Problem Details for HTTP APIs
func (out *Output) SetIssue(failure error, title ...string) {
issue := NewIssue(out.Status)
if len(title) != 0 {
issue.Title = title[0]
}
body, err := json.Marshal(issue)
if err != nil {
out.Status = http.StatusInternalServerError
out.Headers = append(out.Headers,
struct {
Header string
Value string
}{"Content-Type", "text/plain"},
)
out.Body = "JSON serialization is failed for <Issue>"
return
}
out.Headers = append(out.Headers,
struct {
Header string
Value string
}{"Content-Type", "application/json"},
)
out.Body = string(body)
out.Failure = fmt.Errorf("%s: %d %s - %w", issue.ID, out.Status, issue.Title, failure)
}
// Result is a composable function that abstract results of HTTP endpoint.
// The function takes instance of HTTP output and mutates its value
//
// return µ.Status.OK(
// µ.WithHeader(headers.ContentType, headers.ApplicationJson),
// µ.WithJSON(value),
// )
type Result func(*Output) error
// Sequence for generating unique identity
var Sequence = guid.NewClock()
// Issue implements RFC 7807: Problem Details for HTTP APIs
type Issue struct {
ID string `json:"instance"`
Type string `json:"type"`
Status int `json:"status"`
Title string `json:"title"`
}
// NewIssue creates instance of Issue
func NewIssue(status int) Issue {
return Issue{
ID: guid.G(Sequence).String(),
Type: fmt.Sprintf("https://httpstatuses.com/%d", status),
Status: status,
Title: http.StatusText(status),
}
}