14287: Ensure timestamps in responses have 9 digits of nanoseconds.
[arvados.git] / lib / controller / router / response.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package router
6
7 import (
8         "encoding/json"
9         "net/http"
10         "strings"
11         "time"
12
13         "git.curoverse.com/arvados.git/sdk/go/arvados"
14         "git.curoverse.com/arvados.git/sdk/go/httpserver"
15 )
16
17 const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
18
19 type responseOptions struct {
20         Select []string
21 }
22
23 func (rtr *router) responseOptions(opts interface{}) (responseOptions, error) {
24         var rOpts responseOptions
25         switch opts := opts.(type) {
26         case *arvados.GetOptions:
27                 rOpts.Select = opts.Select
28         }
29         return rOpts, nil
30 }
31
32 func (rtr *router) sendResponse(w http.ResponseWriter, resp interface{}, opts responseOptions) {
33         var tmp map[string]interface{}
34         err := rtr.transcode(resp, &tmp)
35         if err != nil {
36                 rtr.sendError(w, err)
37                 return
38         }
39         if len(opts.Select) > 0 {
40                 selected := map[string]interface{}{}
41                 for _, attr := range opts.Select {
42                         if v, ok := tmp[attr]; ok {
43                                 selected[attr] = v
44                         }
45                 }
46                 tmp = selected
47         }
48         // Format non-nil timestamps as rfc3339NanoFixed (by default
49         // they will have been encoded to time.RFC3339Nano, which
50         // omits trailing zeroes).
51         for k, v := range tmp {
52                 if !strings.HasSuffix(k, "_at") {
53                         continue
54                 }
55                 switch tv := v.(type) {
56                 case *time.Time:
57                         if tv == nil {
58                                 break
59                         }
60                         tmp[k] = tv.Format(rfc3339NanoFixed)
61                 case time.Time:
62                         tmp[k] = tv.Format(rfc3339NanoFixed)
63                 case string:
64                         t, err := time.Parse(time.RFC3339Nano, tv)
65                         if err != nil {
66                                 break
67                         }
68                         tmp[k] = t.Format(rfc3339NanoFixed)
69                 }
70         }
71         json.NewEncoder(w).Encode(tmp)
72 }
73
74 func (rtr *router) sendError(w http.ResponseWriter, err error) {
75         code := http.StatusInternalServerError
76         if err, ok := err.(interface{ HTTPStatus() int }); ok {
77                 code = err.HTTPStatus()
78         }
79         httpserver.Error(w, err.Error(), code)
80 }