|
1 |
| -function val = checkDtype(name, type, val) |
| 1 | +function value = checkDtype(name, typeDescriptor, value) |
2 | 2 | %ref
|
3 | 3 | %any, double, int/uint, char
|
4 | 4 | persistent WHITELIST;
|
|
8 | 8 | 'types.untyped.SoftLink'...
|
9 | 9 | };
|
10 | 10 | end
|
11 |
| - |
12 | 11 | %% 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}); |
23 | 42 | else
|
24 |
| - subv = val(names{i}); |
| 43 | + subValue = value(expectedFields{iField}); |
25 | 44 | end
|
26 |
| - assert(isvector(subv),... |
| 45 | + assert(isvector(subValue),... |
27 | 46 | 'NWB:CheckDType:InvalidShape',...
|
28 | 47 | ['struct of arrays as a compound type ',...
|
29 | 48 | 'cannot have multidimensional data in their fields. ',...
|
30 | 49 | 'Field data shape must be scalar or vector to be valid.']);
|
31 |
| - sizes(i) = length(subv); |
| 50 | + fieldSizes(iField) = length(subValue); |
32 | 51 | end
|
33 |
| - sizes = unique(sizes); |
34 |
| - assert(isscalar(sizes),... |
| 52 | + fieldSizes = unique(fieldSizes); |
| 53 | + assert(isscalar(fieldSizes),... |
35 | 54 | 'NWB:CheckDType:InvalidShape',...
|
36 | 55 | ['struct of arrays as a compound type ',...
|
37 | 56 | 'contains mismatched number of elements with unique sizes: [%s]. ',...
|
38 | 57 | 'Number of elements for each struct field must match to be valid.'], ...
|
39 |
| - num2str(sizes)); |
| 58 | + num2str(fieldSizes)); |
40 | 59 | end
|
41 |
| - for i=1:length(names) |
42 |
| - pnm = names{i}; |
43 |
| - subnm = [name '.' pnm]; |
44 |
| - typenm = type.(pnm); |
45 | 60 |
|
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); |
51 | 74 | assert(~iscell(elem) && ...
|
52 | 75 | (isempty(elem) || ...
|
53 | 76 | (isscalar(elem) || (ischar(elem) && isvector(elem)))),...
|
54 | 77 | 'NWB:CheckDType:InvalidType',...
|
55 | 78 | ['Fields for an array of structs for '...
|
56 | 79 | '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); |
58 | 81 | end
|
59 | 82 | 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})); |
61 | 85 | end
|
62 | 86 | end
|
63 | 87 | return;
|
64 | 88 | end
|
65 | 89 |
|
66 | 90 |
|
67 | 91 | %% 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 |
70 | 113 | return;
|
71 | 114 | end
|
72 | 115 |
|
73 | 116 | % retrieve sample of val
|
74 |
| -if isa(val, 'types.untyped.DataStub') |
| 117 | +if isa(value, 'types.untyped.DataStub') |
75 | 118 | %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 = []; |
79 | 122 | else
|
80 |
| - val = val.load(1); |
| 123 | + value = value.load(1); |
81 | 124 | 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); |
92 | 135 | else
|
93 | 136 | valueWrapper = [];
|
94 | 137 | end
|
95 | 138 |
|
96 |
| -correctedValue = types.util.correctType(val, type); |
| 139 | +correctedValue = types.util.correctType(value, typeDescriptor); |
97 | 140 | % this specific conversion is fine as HDF5 doesn't have a representative
|
98 | 141 | % datetime type. Thus we suppress the warning for this case.
|
99 | 142 | isDatetimeConversion = isa(correctedValue, 'datetime')...
|
100 |
| - && (ischar(val) || isstring(val) || iscellstr(val)); |
| 143 | + && (ischar(value) || isstring(value) || iscellstr(value)); |
101 | 144 | if ~isempty(valueWrapper) ...
|
102 |
| - && ~strcmp(class(correctedValue), class(val)) ... |
| 145 | + && ~strcmp(class(correctedValue), class(value)) ... |
103 | 146 | && ~isDatetimeConversion
|
104 | 147 | warning('NWB:CheckDataType:NeedsManualConversion',...
|
105 | 148 | 'Property `%s` is not of type `%s` and should be corrected by the user.', ...
|
106 | 149 | name, class(correctedValue));
|
107 | 150 | else
|
108 |
| - val = correctedValue; |
| 151 | + value = correctedValue; |
109 | 152 | end
|
110 | 153 |
|
111 | 154 | % re-wrap value
|
112 | 155 | if ~isempty(valueWrapper)
|
113 |
| - val = valueWrapper; |
| 156 | + value = valueWrapper; |
114 | 157 | end
|
115 | 158 | end
|
0 commit comments