-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocument.go
More file actions
235 lines (196 loc) · 7.21 KB
/
Copy pathdocument.go
File metadata and controls
235 lines (196 loc) · 7.21 KB
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
package jsonstat
import (
"slices"
)
// Document is the top-level parsed value of a JSON-stat response. Depending
// on the source document's "class", exactly one of the fields below is
// populated:
//
// - class "dataset": [Document.Dataset] holds the single dataset. (Some
// 2.0 documents omit "class" entirely; [Decode] treats these as datasets
// when they have the required dataset properties.)
// - class "collection": [Document.Collection] holds the collection.
// - class "dimension": [Document.Dimension] holds the standalone dimension.
//
// The pre-2.0 bundle container is also accepted: [Document.Bundle] is a map
// of datasets keyed by id, and [Document.IsBundle] reports this case. Bundles
// are decoded for backward compatibility but never re-emitted by [Encode].
type Document struct {
// Class is the JSON-stat response class declared on the document. It is
// one of [ClassDataset], [ClassCollection], [ClassDimension], or
// [ClassBundle] (the legacy pre-2.0 marker, synthesised by Decode).
Class string
// Version is the JSON-stat version declared on the document.
Version string
// Label is the document-level label, if any.
Label string
// Href is the document-level canonical URL, if any.
Href string
// Note is the document-level notes array.
Note []string
// Link is the document-level related-resource links.
Link []Link
// Extension collects provider-specific extra fields attached to the
// document itself.
Extension map[string]any
// Dataset holds the parsed dataset when Class == [ClassDataset]. It is
// nil otherwise.
Dataset *Dataset
// Collection holds the parsed collection when Class == [ClassCollection].
Collection *Collection
// Dimension holds the parsed standalone dimension when Class ==
// [ClassDimension]. It is nil otherwise.
Dimension *Dimension
// Bundle holds the parsed datasets when the document is a pre-2.0 bundle
// (a JSON object whose values are datasets, with no "class" property).
// Keyed by dataset id. nil for 2.0 documents.
Bundle map[string]*Dataset
// IsBundle reports whether the document was decoded as a pre-2.0 bundle.
IsBundle bool
}
// IsDataset reports whether the document is a single-dataset response.
func (d *Document) IsDataset() bool { return d != nil && d.Dataset != nil && !d.IsBundle }
// IsCollection reports whether the document is a collection response.
func (d *Document) IsCollection() bool { return d != nil && d.Collection != nil && !d.IsBundle }
// IsDimension reports whether the document is a standalone dimension response.
func (d *Document) IsDimension() bool { return d != nil && d.Dimension != nil && !d.IsBundle }
// Datasets returns every dataset in the document, regardless of class:
//
// - dataset: a one-element slice holding [Document.Dataset].
// - collection: a slice holding each embedded dataset.
// - bundle: a slice holding each bundled dataset, in id-key order.
// - otherwise: nil.
func (d *Document) Datasets() []*Dataset {
if d == nil {
return nil
}
switch {
case d.IsBundle:
keys := make([]string, 0, len(d.Bundle))
for k := range d.Bundle {
keys = append(keys, k)
}
slices.Sort(keys)
out := make([]*Dataset, 0, len(keys))
for _, k := range keys {
out = append(out, d.Bundle[k])
}
return out
case d.Dataset != nil:
return []*Dataset{d.Dataset}
case d.Collection != nil:
return d.Collection.Datasets()
}
return nil
}
// SingleDataset is a convenience for the common case of a single-dataset
// document. It returns [Document.Dataset] when present, or
// [ErrDatasetNotFound] otherwise.
func (d *Document) SingleDataset() (*Dataset, error) {
if d == nil || d.Dataset == nil || d.IsBundle {
return nil, ErrDatasetNotFound
}
return d.Dataset, nil
}
// Item is one entry in a JSON-stat collection. JSON-stat's collection class
// uses a "link" array with rel "item" to enumerate its contents; each item
// may embed a full dataset, dimension, or another collection, or merely link
// to one via Href.
type Item struct {
// Href is the URL of the item, when it is referenced rather than
// embedded.
Href string
// Type is the MIME type of the item at Href.
Type string
// Label is the human-readable item title.
Label string
// Class is the embedded item's class (e.g. "dataset"), when the item is
// embedded rather than only linked.
Class string
// ID is the optional item identifier inside the collection. Some
// providers add an "id" extension to disambiguate items.
ID string
// Extension collects provider-specific extra fields attached to the
// item's link entry.
Extension map[string]any
// EmbeddedDataset is non-nil when the item embeds a full dataset.
EmbeddedDataset *Dataset
// EmbeddedDimension is non-nil when the item embeds a standalone
// dimension.
EmbeddedDimension *Dimension
}
// IsEmbedded reports whether the item carries an embedded response rather
// than only a link.
func (i *Item) IsEmbedded() bool {
return i != nil && (i.EmbeddedDataset != nil || i.EmbeddedDimension != nil)
}
// Collection is the in-memory representation of a JSON-stat collection
// response (class "collection"). A collection is an ordered list of
// [Item] values, each of which may embed or link to a dataset, dimension, or
// another collection.
type Collection struct {
// Label is the collection's human-readable title.
Label string
// Href is the collection's canonical URL.
Href string
// Updated is the collection's last-updated timestamp in ISO 8601 format.
Updated string
// Source is the collection's source attribution text.
Source string
// Note is the collection-level notes.
Note []string
// Link holds the collection's links. Items appear here with rel "item".
Link []Link
// Extension collects provider-specific extra fields.
Extension map[string]any
// Items is the parsed item list, in declaration order.
Items []Item
}
// ItemByIndex returns the i-th item, or [ErrItemNotFound] when out of range.
func (c *Collection) ItemByIndex(i int) (*Item, error) {
if c == nil || i < 0 || i >= len(c.Items) {
return nil, &ValueError{Op: "Collection.ItemByIndex", Flat: i, Err: ErrItemNotFound}
}
return &c.Items[i], nil
}
// ItemByID returns the first item whose [Item.ID] matches id, or
// [ErrItemNotFound] when absent. ID equality is exact-string.
func (c *Collection) ItemByID(id string) (*Item, error) {
if c == nil {
return nil, ErrItemNotFound
}
for i := range c.Items {
if c.Items[i].ID == id {
return &c.Items[i], nil
}
}
return nil, &ValueError{Op: "Collection.ItemByID", Cat: id, Err: ErrItemNotFound}
}
// ItemsByClass returns the items whose embedded class matches the given
// JSON-stat class string (e.g. [ClassDataset]).
func (c *Collection) ItemsByClass(class string) []*Item {
if c == nil {
return nil
}
out := make([]*Item, 0)
for i := range c.Items {
if c.Items[i].Class == class {
out = append(out, &c.Items[i])
}
}
return out
}
// Datasets returns every embedded dataset in the collection, in declaration
// order. Items that only link (without embedding) are skipped.
func (c *Collection) Datasets() []*Dataset {
if c == nil {
return nil
}
out := make([]*Dataset, 0, len(c.Items))
for i := range c.Items {
if c.Items[i].EmbeddedDataset != nil {
out = append(out, c.Items[i].EmbeddedDataset)
}
}
return out
}