8
8
"log/slog"
9
9
"net/http"
10
10
"strconv"
11
+ "time"
11
12
)
12
13
13
14
// NewCompletionHTTPRequest creates an HTTP request deliver an operation completion to a given URL.
@@ -37,21 +38,36 @@ type OperationCompletionSuccessful struct {
37
38
// Body to send in the completion HTTP request.
38
39
// If it implements `io.Closer` it will automatically be closed by the client.
39
40
Body io.Reader
41
+ // OperationID is the unique ID for this operation. Used when a completion callback is received before a started response.
42
+ OperationID string
43
+ // StartTime is the time the operation started. Used when a completion callback is received before a started response.
44
+ StartTime time.Time
45
+ // StartLinks are used to link back to the operation when a completion callback is received before a started response.
46
+ StartLinks []Link
40
47
}
41
48
42
49
// OperationCompletionSuccessfulOptions are options for [NewOperationCompletionSuccessful].
43
50
type OperationCompletionSuccessfulOptions struct {
44
51
// Optional serializer for the result. Defaults to the SDK's default Serializer, which handles JSONables, byte
45
52
// slices and nils.
46
53
Serializer Serializer
54
+ // OperationID is the unique ID for this operation. Used when a completion callback is received before a started response.
55
+ OperationID string
56
+ // StartTime is the time the operation started. Used when a completion callback is received before a started response.
57
+ StartTime time.Time
58
+ // StartLinks are used to link back to the operation when a completion callback is received before a started response.
59
+ StartLinks []Link
47
60
}
48
61
49
62
// NewOperationCompletionSuccessful constructs an [OperationCompletionSuccessful] from a given result.
50
63
func NewOperationCompletionSuccessful (result any , options OperationCompletionSuccessfulOptions ) (* OperationCompletionSuccessful , error ) {
51
64
if reader , ok := result .(* Reader ); ok {
52
65
return & OperationCompletionSuccessful {
53
- Header : addContentHeaderToHTTPHeader (reader .Header , make (http.Header )),
54
- Body : reader .ReadCloser ,
66
+ Header : addContentHeaderToHTTPHeader (reader .Header , make (http.Header )),
67
+ Body : reader .ReadCloser ,
68
+ OperationID : options .OperationID ,
69
+ StartTime : options .StartTime ,
70
+ StartLinks : options .StartLinks ,
55
71
}, nil
56
72
} else {
57
73
content , ok := result .(* Content )
@@ -69,8 +85,11 @@ func NewOperationCompletionSuccessful(result any, options OperationCompletionSuc
69
85
header := http.Header {"Content-Length" : []string {strconv .Itoa (len (content .Data ))}}
70
86
71
87
return & OperationCompletionSuccessful {
72
- Header : addContentHeaderToHTTPHeader (content .Header , header ),
73
- Body : bytes .NewReader (content .Data ),
88
+ Header : addContentHeaderToHTTPHeader (content .Header , header ),
89
+ Body : bytes .NewReader (content .Data ),
90
+ OperationID : options .OperationID ,
91
+ StartTime : options .StartTime ,
92
+ StartLinks : options .StartLinks ,
74
93
}, nil
75
94
}
76
95
}
@@ -80,6 +99,18 @@ func (c *OperationCompletionSuccessful) applyToHTTPRequest(request *http.Request
80
99
request .Header = c .Header .Clone ()
81
100
}
82
101
request .Header .Set (headerOperationState , string (OperationStateSucceeded ))
102
+ if c .Header .Get (HeaderOperationID ) == "" && c .OperationID != "" {
103
+ request .Header .Set (HeaderOperationID , c .OperationID )
104
+ }
105
+ if c .Header .Get (headerOperationStartTime ) == "" && ! c .StartTime .IsZero () {
106
+ request .Header .Set (headerOperationStartTime , c .StartTime .Format (http .TimeFormat ))
107
+ }
108
+ if c .Header .Get (headerLink ) == "" {
109
+ if err := addLinksToHTTPHeader (c .StartLinks , request .Header ); err != nil {
110
+ return err
111
+ }
112
+ }
113
+
83
114
if closer , ok := c .Body .(io.ReadCloser ); ok {
84
115
request .Body = closer
85
116
} else {
@@ -95,6 +126,12 @@ type OperationCompletionUnsuccessful struct {
95
126
Header http.Header
96
127
// State of the operation, should be failed or canceled.
97
128
State OperationState
129
+ // OperationID is the unique ID for this operation. Used when a completion callback is received before a started response.
130
+ OperationID string
131
+ // StartTime is the time the operation started. Used when a completion callback is received before a started response.
132
+ StartTime time.Time
133
+ // StartLinks are used to link back to the operation when a completion callback is received before a started response.
134
+ StartLinks []Link
98
135
// Failure object to send with the completion.
99
136
Failure * Failure
100
137
}
@@ -105,6 +142,17 @@ func (c *OperationCompletionUnsuccessful) applyToHTTPRequest(request *http.Reque
105
142
}
106
143
request .Header .Set (headerOperationState , string (c .State ))
107
144
request .Header .Set ("Content-Type" , contentTypeJSON )
145
+ if c .Header .Get (HeaderOperationID ) == "" && c .OperationID != "" {
146
+ request .Header .Set (HeaderOperationID , c .OperationID )
147
+ }
148
+ if c .Header .Get (headerOperationStartTime ) == "" && ! c .StartTime .IsZero () {
149
+ request .Header .Set (headerOperationStartTime , c .StartTime .Format (http .TimeFormat ))
150
+ }
151
+ if c .Header .Get (headerLink ) == "" {
152
+ if err := addLinksToHTTPHeader (c .StartLinks , request .Header ); err != nil {
153
+ return err
154
+ }
155
+ }
108
156
109
157
b , err := json .Marshal (c .Failure )
110
158
if err != nil {
@@ -121,6 +169,12 @@ type CompletionRequest struct {
121
169
HTTPRequest * http.Request
122
170
// State of the operation.
123
171
State OperationState
172
+ // OperationID is the ID of the operation. Used when a completion callback is received before a started response.
173
+ OperationID string
174
+ // StartTime is the time the operation started. Used when a completion callback is received before a started response.
175
+ StartTime time.Time
176
+ // StartLinks are used to link back to the operation when a completion callback is received before a started response.
177
+ StartLinks []Link
124
178
// Parsed from request and set if State is failed or canceled.
125
179
Failure * Failure
126
180
// Extracted from request and set if State is succeeded.
@@ -154,8 +208,21 @@ func (h *completionHTTPHandler) ServeHTTP(writer http.ResponseWriter, request *h
154
208
ctx := request .Context ()
155
209
completion := CompletionRequest {
156
210
State : OperationState (request .Header .Get (headerOperationState )),
211
+ OperationID : request .Header .Get (HeaderOperationID ),
157
212
HTTPRequest : request ,
158
213
}
214
+ if startTimeHeader := request .Header .Get (headerOperationStartTime ); startTimeHeader != "" {
215
+ var parseTimeErr error
216
+ if completion .StartTime , parseTimeErr = http .ParseTime (startTimeHeader ); parseTimeErr != nil {
217
+ h .writeFailure (writer , HandlerErrorf (HandlerErrorTypeBadRequest , "failed to parse operation start time header" ))
218
+ return
219
+ }
220
+ }
221
+ var decodeErr error
222
+ if completion .StartLinks , decodeErr = getLinksFromHeader (request .Header ); decodeErr != nil {
223
+ h .writeFailure (writer , HandlerErrorf (HandlerErrorTypeBadRequest , "failed to decode links from request headers" ))
224
+ return
225
+ }
159
226
switch completion .State {
160
227
case OperationStateFailed , OperationStateCanceled :
161
228
if ! isMediaTypeJSON (request .Header .Get ("Content-Type" )) {
0 commit comments