Skip to content

Commit bbf83ef

Browse files
committed
Major refactor to checkDType
- expand and clarify variable names - Add order and field existence checks to compound data types - convert empty types to their expected values if possible.
1 parent 8524cde commit bbf83ef

File tree

1 file changed

+94
-51
lines changed

1 file changed

+94
-51
lines changed

+types/+util/checkDtype.m

+94-51
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function val = checkDtype(name, type, val)
1+
function value = checkDtype(name, typeDescriptor, value)
22
%ref
33
%any, double, int/uint, char
44
persistent WHITELIST;
@@ -8,108 +8,151 @@
88
'types.untyped.SoftLink'...
99
};
1010
end
11-
1211
%% compound type processing
13-
if isstruct(type)
14-
names = fieldnames(type);
15-
assert(isstruct(val) || istable(val) || isa(val, 'containers.Map'), ...
16-
'types.untyped.checkDtype: Compound Type must be a struct, table, or a containers.Map');
17-
if (isstruct(val) && isscalar(val)) || isa(val, 'containers.Map')
18-
%check for correct array shape
19-
sizes = zeros(length(names),1);
20-
for i=1:length(names)
21-
if isstruct(val)
22-
subv = val.(names{i});
12+
if isstruct(typeDescriptor)
13+
expectedFields = fieldnames(typeDescriptor);
14+
assert(isstruct(value) || istable(value) || isa(value, 'containers.Map') ...
15+
, 'NWB:CheckDType:InvalidValue' ...
16+
, 'Compound Type must be a struct, table, or a containers.Map' ...
17+
);
18+
19+
% assert field names and order of fields is correct.
20+
if isstruct(value)
21+
valueFields = fieldnames(value);
22+
else % table
23+
valueFields = value.Properties.VariableNames;
24+
end
25+
assert(isempty(setdiff(expectedFields, valueFields)) ...
26+
, 'NWB:CheckDType:InvalidValue' ...
27+
, 'Compound type must only contain fields (%s)', strjoin(expectedFields, ', ') ...
28+
);
29+
for iField = 1:length(expectedFields)
30+
assert(strcmp(expectedFields{iField}, valueFields{iField}) ...
31+
, 'NWB:CheckDType:InvalidValue' ...
32+
, 'Compound fields are out of order.\nExpected (%s) Got (%s)' ...
33+
, strjoin(expectedFields, ', '), strjoin(valueFields, ', '));
34+
end
35+
36+
if (isstruct(value) && isscalar(value)) || isa(value, 'containers.Map')
37+
% check for correct array shape
38+
fieldSizes = zeros(length(expectedFields),1);
39+
for iField = 1:length(expectedFields)
40+
if isstruct(value)
41+
subValue = value.(expectedFields{iField});
2342
else
24-
subv = val(names{i});
43+
subValue = value(expectedFields{iField});
2544
end
26-
assert(isvector(subv),...
45+
assert(isvector(subValue),...
2746
'NWB:CheckDType:InvalidShape',...
2847
['struct of arrays as a compound type ',...
2948
'cannot have multidimensional data in their fields. ',...
3049
'Field data shape must be scalar or vector to be valid.']);
31-
sizes(i) = length(subv);
50+
fieldSizes(iField) = length(subValue);
3251
end
33-
sizes = unique(sizes);
34-
assert(isscalar(sizes),...
52+
fieldSizes = unique(fieldSizes);
53+
assert(isscalar(fieldSizes),...
3554
'NWB:CheckDType:InvalidShape',...
3655
['struct of arrays as a compound type ',...
3756
'contains mismatched number of elements with unique sizes: [%s]. ',...
3857
'Number of elements for each struct field must match to be valid.'], ...
39-
num2str(sizes));
58+
num2str(fieldSizes));
4059
end
41-
for i=1:length(names)
42-
pnm = names{i};
43-
subnm = [name '.' pnm];
44-
typenm = type.(pnm);
4560

46-
if (isstruct(val) && isscalar(val)) || istable(val)
47-
val.(pnm) = types.util.checkDtype(subnm,typenm,val.(pnm));
48-
elseif isstruct(val)
49-
for j=1:length(val)
50-
elem = val(j).(pnm);
61+
for iField = 1:length(expectedFields)
62+
% validate subfield types.
63+
name = expectedFields{iField};
64+
subName = [name '.' name];
65+
subType = typeDescriptor.(name);
66+
67+
if (isstruct(value) && isscalar(value)) || istable(value)
68+
% scalar struct or table with columns.
69+
value.(name) = types.util.checkDtype(subName,subType,value.(name));
70+
elseif isstruct(value)
71+
% array of structs
72+
for j=1:length(value)
73+
elem = value(j).(name);
5174
assert(~iscell(elem) && ...
5275
(isempty(elem) || ...
5376
(isscalar(elem) || (ischar(elem) && isvector(elem)))),...
5477
'NWB:CheckDType:InvalidType',...
5578
['Fields for an array of structs for '...
5679
'compound types should have non-cell scalar values or char arrays.']);
57-
val(j).(pnm) = types.util.checkDtype(subnm, typenm, elem);
80+
value(j).(name) = types.util.checkDtype(subName, subType, elem);
5881
end
5982
else
60-
val(names{i}) = types.util.checkDtype(subnm,typenm,val(names{i}));
83+
value(expectedFields{iField}) = types.util.checkDtype( ...
84+
subName, subType, value(expectedFields{iField}));
6185
end
6286
end
6387
return;
6488
end
6589

6690

6791
%% primitives
68-
if isempty(val) ... % MATLAB's "null" operator. Even if it's numeric, you can replace it with any class.
69-
|| isa(val, 'types.untyped.SoftLink') % Softlinks cannot be validated at this level.
92+
93+
if isa(value, 'types.untyped.SoftLink')
94+
% Softlinks cannot be validated at this level.
95+
return;
96+
end
97+
98+
if isempty(value)
99+
% MATLAB's "null" operator. Even if it's numeric, you can replace it with any class.
100+
% we can replace empty values with their equivalents, however.
101+
replaceableNullTypes = {...
102+
'char' ...
103+
, 'logical' ...
104+
, 'single', 'double' ...
105+
, 'int8', 'uint8' ...
106+
, 'int16', 'uint16' ...
107+
, 'int32', 'uint32' ...
108+
, 'int64', 'uint64' ...
109+
};
110+
if ischar(typeDescriptor) && any(strcmp(typeDescriptor, replaceableNullTypes))
111+
value = cast(value, typeDescriptor);
112+
end
70113
return;
71114
end
72115

73116
% retrieve sample of val
74-
if isa(val, 'types.untyped.DataStub')
117+
if isa(value, 'types.untyped.DataStub')
75118
%grab first element and check
76-
valueWrapper = val;
77-
if any(val.dims == 0)
78-
val = [];
119+
valueWrapper = value;
120+
if any(value.dims == 0)
121+
value = [];
79122
else
80-
val = val.load(1);
123+
value = value.load(1);
81124
end
82-
elseif isa(val, 'types.untyped.Anon')
83-
valueWrapper = val;
84-
val = val.value;
85-
elseif isa(val, 'types.untyped.ExternalLink') &&...
86-
~strcmp(type, 'types.untyped.ExternalLink')
87-
valueWrapper = val;
88-
val = val.deref();
89-
elseif isa(val, 'types.untyped.DataPipe')
90-
valueWrapper = val;
91-
val = cast([], val.dataType);
125+
elseif isa(value, 'types.untyped.Anon')
126+
valueWrapper = value;
127+
value = value.value;
128+
elseif isa(value, 'types.untyped.ExternalLink') &&...
129+
~strcmp(typeDescriptor, 'types.untyped.ExternalLink')
130+
valueWrapper = value;
131+
value = value.deref();
132+
elseif isa(value, 'types.untyped.DataPipe')
133+
valueWrapper = value;
134+
value = cast([], value.dataType);
92135
else
93136
valueWrapper = [];
94137
end
95138

96-
correctedValue = types.util.correctType(val, type);
139+
correctedValue = types.util.correctType(value, typeDescriptor);
97140
% this specific conversion is fine as HDF5 doesn't have a representative
98141
% datetime type. Thus we suppress the warning for this case.
99142
isDatetimeConversion = isa(correctedValue, 'datetime')...
100-
&& (ischar(val) || isstring(val) || iscellstr(val));
143+
&& (ischar(value) || isstring(value) || iscellstr(value));
101144
if ~isempty(valueWrapper) ...
102-
&& ~strcmp(class(correctedValue), class(val)) ...
145+
&& ~strcmp(class(correctedValue), class(value)) ...
103146
&& ~isDatetimeConversion
104147
warning('NWB:CheckDataType:NeedsManualConversion',...
105148
'Property `%s` is not of type `%s` and should be corrected by the user.', ...
106149
name, class(correctedValue));
107150
else
108-
val = correctedValue;
151+
value = correctedValue;
109152
end
110153

111154
% re-wrap value
112155
if ~isempty(valueWrapper)
113-
val = valueWrapper;
156+
value = valueWrapper;
114157
end
115158
end

0 commit comments

Comments
 (0)