-
Notifications
You must be signed in to change notification settings - Fork 9
/
HashDataType.qc
246 lines (219 loc) · 9.84 KB
/
HashDataType.qc
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
# -*- mode: qore; indent-tabs-mode: nil -*-
#! Qore HashDataType class definition
/** HashDataType.qc Copyright 2019 - 2022 Qore Technologies, s.r.o.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#! contains all public definitions in the DataProvider module
public namespace DataProvider {
#! describes a data type based on a hash
/** @note objects of this type are only compatible if their fields are compatible; non-matching fields of other types
are automatically considered compatible; change the default "other field type" by calling
setDefaultOtherFieldType() to change this; calling setDefaultOtherFieldType() with no argument removes
compatibility with non-matching fields in other types.
Additionally, adding any fields before calling setDefaultOtherFieldType() will also remove compatibility with
unknown fields
*/
public class HashDataType inherits QoreDataType {
private {
#! the name of the type
string name;
#! fields
hash<string, AbstractDataField> fields;
#! allow other fields
*AbstractDataProviderType default_other_field_type =
AbstractDataProviderType::get(AbstractDataProviderType::anyType);
#! default other field type set mamnually?
bool manual_default_other_field_type = False;
#! if the type requires validation
bool has_default_other_field_type = False;
}
#! creates the object and assigns the name as the type
constructor(string name = AutoHashType.getName(), *hash<auto> options, *hash<auto> tags)
: QoreDataType(AutoHashType, options, tags) {
self.name = name;
}
#! creates the object and assigns the name as the given name or the base type's name
constructor(Type base_type, *string name, *hash<auto> options, *hash<auto> tags)
: QoreDataType(base_type, options, tags) {
self.name = name ?? base_type.getName();
}
#! creates the object from the given record description and assigns the name as the type
constructor(string name = AutoHashType.getName(), hash<string, AbstractDataField> fields, *hash<auto> options,
*hash<auto> tags) : QoreDataType(AutoHashType, options, tags) {
self.name = name;
self.fields = fields;
}
#! Sets the default field type for unlisted fields
setDefaultOtherFieldType(*AbstractDataProviderType default_other_field_type) {
self.default_other_field_type = default_other_field_type;
if (default_other_field_type) {
has_default_other_field_type = default_other_field_type.hasType();
} else {
has_default_other_field_type = False;
}
manual_default_other_field_type = True;
}
#! Returns True if the type has a default field type for undeclared fields
bool hasDefaultOtherFieldType() {
return has_default_other_field_type;
}
#! Returns the default field type for undeclared fields, if any
*AbstractDataProviderType getDefaultOtherFieldType() {
return default_other_field_type;
}
#! Returns the type name
string getName() {
return name;
}
#! Returns the value if the value can be assigned to the type
/** @param input_value the value to assign to the type
@return the value to be assigned; can be converted by the type
@throw RUNTIME-TYPE-ERROR value cannot be assigned to type
*/
auto acceptsValue(auto input_value) {
# specific check to provide a better error message in case of a missing argument
on_error if (!exists input_value && fields && $1.err == "RUNTIME-TYPE-ERROR") {
rethrow "MISSING-VALUE-ERROR", sprintf("no input value provided for hash with defined fields: %y",
keys fields);
}
# {} + ensure that the hash value is "hash<auto>"
auto value = {} + QoreDataType::acceptsValue(input_value);
hash<auto> value_copy = value;
# check fields if any are defined
foreach AbstractDataField field in (fields.iterator()) {
string key = field.getName();
auto val = remove value_copy{key};
if (!exists val && !field.isMandatory()) {
continue;
}
try {
value{key} = field.acceptsValue(val);
} catch (hash<ExceptionInfo> ex) {
rethrow "RUNTIME-TYPE-ERROR", sprintf("error in type %y field %y: %s: %s", getName(), key, ex.err,
ex.desc);
}
}
if (value_copy) {
if (default_other_field_type) {
if (has_default_other_field_type) {
foreach hash<auto> i in (value_copy.pairIterator()) {
try {
value{i.key} = default_other_field_type.acceptsValue(i.value);
} catch (hash<ExceptionInfo> ex) {
rethrow "RUNTIME-TYPE-ERROR", sprintf("error in type %y default field %y: %s: %s", getName(),
i.key, ex.err, ex.desc);
}
}
} else {
value += value_copy;
}
} else if (fields) {
throw "RUNTIME-TYPE-ERROR", sprintf("error in type %y unsupported fields in value: %y; known fields: %y",
getName(), keys value_copy, keys fields);
}
}
return value;
}
#! Returns True if this type can be assigned from values of the argument type
/** @note objects of this type are only compatible if their fields are compatible; if either side is a hash
without declared fields, then they are compatible
*/
bool isAssignableFrom(AbstractDataProviderType t) {
*hash<string, AbstractDataField> other_fields = t.getFields();
if (fields && other_fields) {
foreach AbstractDataField field in (fields.iterator()) {
string key = field.getName();
*AbstractDataField other_field = remove other_fields{key};
if (!other_field) {
if (!field.isMandatory()) {
continue;
} else {
return False;
}
}
if (!field.getType().isAssignableFrom(other_field.getType())) {
return False;
}
}
if (other_fields) {
if (!default_other_field_type) {
return False;
}
if (has_default_other_field_type) {
return True;
}
foreach AbstractDataField other_field in (other_fields.iterator()) {
if (!default_other_field_type.isAssignableFrom(other_field.getType())) {
return False;
}
}
}
}
return QoreDataType::isAssignableFrom(t);
}
#! adds a field to the type
HashDataType addField(AbstractDataField field) {
fields{field.getName()} = field;
# do not allow unnamed fields if any fields are added manually and no default other field type has been set
# manually
if (default_other_field_type && !manual_default_other_field_type) {
remove default_other_field_type;
}
return self;
}
#! Returns the given field, if present, or @ref nothing if not
*AbstractDataField getField(string name) {
return fields{name};
}
#! Returns the fields of the data structure; if any
*hash<string, AbstractDataField> getFields() {
return fields;
}
#! Returns a "soft" type equivalent to the current type
/** @return a "soft" type equivalent to the current type
*/
AbstractDataProviderType getSoftType() {
if (soft_type) {
return self;
}
HashDataType rv(type, name, options);
if (manual_default_other_field_type) {
rv.setDefaultOtherFieldType(default_other_field_type);
}
map rv.addField($1.getSoftType()), getFields().iterator();
rv.soft_type = True;
return rv;
}
#! returns a description of the type as a hash
hash<DataTypeInfo> getInfo() {
hash<DataTypeInfo> rv = QoreDataType::getInfo();
if (!fields && default_other_field_type) {
rv.default_field_type_info = default_other_field_type.getInfo();
}
return rv;
}
#! Returns an "or nothing" type equivalent to the current type
/** @return an "or nothing" type equivalent to the current type
*/
AbstractDataProviderType getOrNothingType() {
# recursively return "or nothing" typed fields in any case
HashDataType rv = QoreDataType::getOrNothingType();
map rv.fields{$1.key} = $1.value.getOrNothingType(), fields.pairIterator();
return rv;
}
}
}