/
primitive.go
267 lines (249 loc) · 8.21 KB
/
primitive.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
// Copyright 2020 Google LLC
//
// 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 jsonformat
import (
"encoding/base64"
"encoding/json"
"fmt"
"sort"
"strings"
"github.com/google/fhir/go/jsonformat/internal/accessor"
"github.com/google/fhir/go/jsonformat/internal/jsonpbhelper"
"github.com/google/fhir/go/jsonformat/internal/protopath"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
// protoToExtension converts a FHIR extension proto (e.g. Base64BinarySeparatorStride or
// PrimitiveHasNoValue) into a Google-defined FHIR extension describing the proto.
func protoToExtension(pb, ext proto.Message) error {
rpb := pb.ProtoReflect()
url, err := jsonpbhelper.InternalExtensionURL(rpb.Descriptor())
if err != nil {
return err
}
ret := ext.ProtoReflect()
if err := accessor.SetValue(ret, url, "url", "value"); err != nil {
return fmt.Errorf("setting url value in a new extension proto: %v, err: %w", ret.Interface(), err)
}
// Iterate over fields in deterministic order by sorting by number.
type descAndVal struct {
desc protoreflect.FieldDescriptor
val protoreflect.Value
}
var setFields []descAndVal
rpb.Range(func(fd protoreflect.FieldDescriptor, val protoreflect.Value) bool {
setFields = append(setFields, descAndVal{fd, val})
return true
})
sort.Slice(setFields, func(i, j int) bool { return setFields[i].desc.Number() < setFields[j].desc.Number() })
// Add an extension for each populated primitive type proto field.
for i := range setFields {
f, val := setFields[i].desc, setFields[i].val
d := f.Message()
if d == nil {
return fmt.Errorf("unexpected field type %v, want Message type", f.Kind())
}
if !jsonpbhelper.IsPrimitiveType(d) {
return fmt.Errorf("unexpected field %v of non-primitive type %v", f.Name(), d.Name())
}
if f.Cardinality() == protoreflect.Repeated {
return fmt.Errorf("field %v is repeated", f.Name())
}
if len(setFields) == 1 {
switch d.Name() {
case "Boolean":
if err := accessor.SetValue(ret, val.Message().Interface(), "value", "choice", "boolean"); err != nil {
return fmt.Errorf("setting boolean to extension value in extension: %v, err: %w", ret.Interface(), err)
}
default:
return fmt.Errorf("unsupported message type: %v", d.Name())
}
return nil
}
e := ret.New()
if err := accessor.SetValue(e, string(f.Name()), "url", "value"); err != nil {
return err
}
switch d.Name() {
case "String":
if err := accessor.SetValue(e, val.Message().Interface(), "value", "choice", "string_value"); err != nil {
return fmt.Errorf("setting string to extension value in extension: %v, err: %w", e.Interface(), err)
}
case "PositiveInt":
if err := accessor.SetValue(e, val.Message().Interface(), "value", "choice", "positive_int"); err != nil {
return fmt.Errorf("setting poisitive integer to extension value in extension: %v, err: %w", e.Interface(), err)
}
default:
return fmt.Errorf("unsupported message type: %v", f.Message().Name())
}
if err := accessor.AppendValue(ret, e.Interface().(proto.Message), "extension"); err != nil {
return fmt.Errorf("appending extension to extension list in extension: %v, err: %w", ret.Interface(), err)
}
}
return nil
}
// parseDecimal parses a FHIR decimal data object into a Decimal proto message, m.
func parseDecimal(decimal json.RawMessage, m proto.Message) error {
mr := m.ProtoReflect()
fn := mr.Descriptor().FullName()
regex, has := jsonpbhelper.RegexValues[fn]
if !has {
return fmt.Errorf("regex not found for %v type", fn)
}
if !regex.MatchString(string(decimal)) {
return fmt.Errorf("invalid decimal: %v", decimal)
}
if err := accessor.SetValue(mr, string(decimal), "value"); err != nil {
return err
}
return nil
}
// Base64BinarySeparatorStrideCreator defines a type of functions that, given the separator string
// and stride value, returns a new Base64BinarySeparatorStride proto.
type base64BinarySeparatorStrideCreator func(sep string, stride uint32) proto.Message
type base64Data struct {
data []byte
stride int
sep int
}
// filterBase64Spaces removes spaces according to the stride/separator encoding. An error is returned for inconsistent stride and separator lengths.
// The return values are: the final length of the string, the detected stride and separator lengths, and an error if one occurred.
func filterBase64Spaces(p []byte) (nn, stride, sep int, err error) {
n := len(p)
chunkStart := 0
for i := 0; i < n; i++ {
c := p[i]
if c != ' ' {
if i != nn {
p[nn] = c
}
nn++
continue
}
curStride := i - chunkStart
if stride == 0 {
stride = curStride
}
if curStride != stride {
return 0, 0, 0, fmt.Errorf("found stride of length %d, previous stride was %d", curStride, stride)
}
chunkStart = i
for ; i < n && p[i] == ' '; i++ {
}
curSep := i - chunkStart
if sep == 0 {
sep = curSep
}
if curSep != sep {
return 0, 0, 0, fmt.Errorf("found separator of length %d, previous separator was %d", curSep, sep)
}
chunkStart = i
i--
}
return nn, stride, sep, nil
}
func decodeBase64(data []byte) (base64Data, error) {
n, stride, sep, err := filterBase64Spaces(data)
if err != nil {
return base64Data{}, err
}
data = data[:n]
n, err = base64.StdEncoding.Decode(data, data)
if err != nil {
return base64Data{}, err
}
return base64Data{
data: data[:n],
stride: stride,
sep: sep,
}, nil
}
// parseBinary parses a FHIR Binary resource object into a Binary proto message, m.
func parseBinary(binary json.RawMessage, m proto.Message, createSepStride base64BinarySeparatorStrideCreator) error {
if len(binary) < 2 || binary[0] != '"' || binary[len(binary)-1] != '"' {
return fmt.Errorf("binary data is not a string")
}
val, err := decodeBase64(binary[1 : len(binary)-1])
if err != nil {
return err
}
if val.stride > 0 {
mr := m.ProtoReflect()
extList, err := accessor.GetList(mr, "extension")
if err != nil {
return err
}
ext := extList.NewElement().Message().Interface().(proto.Message)
sepAndStride := createSepStride(strings.Repeat(" ", val.sep), uint32(val.stride))
if err := protoToExtension(sepAndStride, ext); err != nil {
return err
}
if err := protopath.Set(m, protopath.NewPath("extension.-1"), ext); err != nil {
return err
}
}
return protopath.Set(m, protopath.NewPath("value"), val.data)
}
// serializeBinary serializes proto representation of a FHIR Binary data object into a JSON message.
func serializeBinary(binary proto.Message) (string, error) {
ext, err := jsonpbhelper.GetExtension(binary, jsonpbhelper.Base64BinarySeparatorStrideURL)
if err != nil {
return "", err
}
rBinary := binary.ProtoReflect()
binVal, err := accessor.GetBytes(rBinary, "value")
if err != nil {
return "", err
}
encoded := base64.StdEncoding.EncodeToString(binVal)
if ext == nil {
return encoded, nil
}
if err := jsonpbhelper.RemoveExtension(binary, jsonpbhelper.Base64BinarySeparatorStrideURL); err != nil {
return "", err
}
extList, err := accessor.GetList(ext.ProtoReflect(), "extension")
if err != nil {
return "", err
}
sep := ""
stride := 0
for i := 0; i < extList.Len(); i++ {
e := extList.Get(i)
urlVal, err := accessor.GetString(e.Message(), "url", "value")
if err != nil {
return "", err
}
switch urlVal {
case "separator":
sep, err = accessor.GetString(e.Message(), "value", "choice", "string_value", "value")
if err != nil {
return "", err
}
case "stride":
ustride, err := accessor.GetUint32(e.Message(), "value", "choice", "positive_int", "value")
if err != nil {
return "", err
}
stride = int(ustride)
}
}
ret := ""
pos := 0
for ; (pos+1)*stride < len(encoded); pos++ {
ret = ret + encoded[pos*stride:(pos+1)*stride] + sep
}
ret = ret + encoded[pos*stride:]
return ret, nil
}