@@ -30,7 +30,8 @@ pub fn ParseResult(table_schema: type) type {
3030 /// Error occurred while parsing structured value
3131 @"error" : struct {
3232 /// The kind of structure error that occurred
33- kind : StructureError ,
33+ /// Either a StructureError or TableError
34+ kind : anyerror ,
3435 /// The name of the field that caused the error
3536 field_name : ? []const u8 ,
3637 /// The expected type of the field that caused the error
@@ -95,11 +96,9 @@ pub fn StructuredTable(table_schema: type) type {
9596 /// Convert a data-row index to the corresponding underlying table index.
9697 ///
9798 /// The underlying `Table` stores the header row at table index 0, while
98- /// data rows start at 1. This helper maps a data-row index (or `null` to
99- /// indicate append) to the `Table` insert index.
100- fn headerAwareToTableIndex (data_index : ? usize ) ? usize {
101- // If `data_index` is null, that represents "append" — forward null to Table.insertEmptyRow.
102- return if (data_index ) | i | i + 1 else null ;
99+ /// data rows start at 1. This helper maps a data-row index to the `Table` insert index.
100+ fn headerAwareToTableIndex (data_index : usize ) usize {
101+ return data_index + 1 ;
103102 }
104103
105104 /// Convert an underlying table index to a data-row index.
@@ -110,6 +109,85 @@ pub fn StructuredTable(table_schema: type) type {
110109 return table_index - 1 ;
111110 }
112111
112+ /// Deserialize a CSV value into the appropriate field type
113+ fn deserializeCsvValue (self : Self , comptime T : type , value : []const u8 ) (TableError || StructureError )! T {
114+ const type_info = @typeInfo (T );
115+ if (type_info == .pointer and
116+ type_info .pointer .size == .slice and
117+ type_info .pointer .child == u8 )
118+ {
119+ return value ;
120+ }
121+ switch (type_info ) {
122+ .optional = > {
123+ const child_type = type_info .optional .child ;
124+ if (value .len == 0 ) {
125+ return null ;
126+ } else {
127+ return try self .deserializeCsvValue (child_type , value );
128+ }
129+ },
130+ .bool = > {
131+ const lower = std .ascii .allocLowerString (self .allocator , value ) catch return TableError .OutOfMemory ;
132+ defer self .allocator .free (lower );
133+ for ([_ ][]const u8 { "true" , "1" , "yes" , "y" }) | true_word | {
134+ if (std .mem .eql (u8 , true_word , lower )) {
135+ return true ;
136+ }
137+ }
138+ for ([_ ][]const u8 { "false" , "0" , "no" , "n" }) | false_word | {
139+ if (std .mem .eql (u8 , false_word , lower )) {
140+ return false ;
141+ }
142+ }
143+ return StructureError .UnexpectedType ;
144+ },
145+ .int = > {
146+ return std .fmt .parseInt (T , value , 0 ) catch StructureError .UnexpectedType ;
147+ },
148+ .float = > {
149+ return std .fmt .parseFloat (T , value ) catch StructureError .UnexpectedType ;
150+ },
151+ else = > {
152+ @compileError (std .fmt .comptimePrint ("unsupported field type for '{}'" , .{@typeName (type_info )}));
153+ },
154+ }
155+ }
156+
157+ /// Serialize a field value into a CSV-compatible string
158+ fn serializeCsvValue (self : * Self , comptime T : type , value : T ) TableError ! []const u8 {
159+ const type_info = @typeInfo (T );
160+ if (type_info == .pointer and
161+ type_info .pointer .size == .slice and
162+ type_info .pointer .child == u8 )
163+ {
164+ return value ;
165+ }
166+ switch (type_info ) {
167+ .optional = > {
168+ const child_type = type_info .optional .child ;
169+ if (value == null ) {
170+ return "" ;
171+ } else {
172+ return try self .serializeCsvValue (child_type , value .? );
173+ }
174+ },
175+ .bool = > {
176+ if (value ) {
177+ return "true" ;
178+ } else {
179+ return "false" ;
180+ }
181+ },
182+ .int , .float = > {
183+ return std .fmt .allocPrint (self .arena_allocator .allocator (), "{d}" , .{value }) catch TableError .OutOfMemory ;
184+ },
185+ else = > {
186+ @compileError (std .fmt .comptimePrint ("unsupported field type for '{}'" , .{@typeName (type_info )}));
187+ },
188+ }
189+ }
190+
113191 /// Get a structured row from the StructuredTable by index
114192 ///
115193 /// Example looping through all rows:
@@ -131,7 +209,6 @@ pub fn StructuredTable(table_schema: type) type {
131209 var out : table_schema = undefined ;
132210 inline for (schema_info .@"struct" .fields ) | field | {
133211 const field_name = field .name ;
134- const field_type = @typeInfo (field .type );
135212 const column_indexes = self .table .findColumnIndexesByValue (self .allocator , 0 , field_name ) catch return ParseResult (table_schema ){
136213 .@"error" = .{
137214 .kind = StructureError .MissingColumn ,
@@ -159,63 +236,15 @@ pub fn StructuredTable(table_schema: type) type {
159236 };
160237 defer self .allocator .free (rows );
161238 const value = rows [row_index + 1 ];
162- if (field_type == .pointer and
163- field_type .pointer .size == .slice and
164- field_type .pointer .child == u8 )
165- {
166- @field (out , field_name ) = value ;
167- continue ;
168- }
169- switch (field_type ) {
170- .bool = > {
171- const lower = std .ascii .allocLowerString (self .allocator , value ) catch return TableError .OutOfMemory ;
172- defer self .allocator .free (lower );
173- var matched = false ;
174- for ([_ ][]const u8 { "true" , "1" , "yes" , "y" }) | true_word | {
175- if (std .mem .eql (u8 , true_word , lower )) {
176- @field (out , field_name ) = true ;
177- matched = true ;
178- }
179- }
180- for ([_ ][]const u8 { "false" , "0" , "no" , "n" }) | false_word | {
181- if (std .mem .eql (u8 , false_word , lower )) {
182- @field (out , field_name ) = false ;
183- matched = true ;
184- }
185- }
186- if (! matched ) return ParseResult (table_schema ){
187- .@"error" = .{
188- .kind = StructureError .UnexpectedType ,
189- .field_name = field_name ,
190- .field_type = @typeName (field .type ),
191- .csv_value = value ,
192- },
193- };
194- },
195- .int = > {
196- @field (out , field_name ) = std .fmt .parseInt (field .type , value , 0 ) catch return ParseResult (table_schema ){
197- .@"error" = .{
198- .kind = StructureError .UnexpectedType ,
199- .field_name = field_name ,
200- .field_type = @typeName (field .type ),
201- .csv_value = value ,
202- },
203- };
204- },
205- .float = > {
206- @field (out , field_name ) = std .fmt .parseFloat (field .type , value ) catch return ParseResult (table_schema ){
207- .@"error" = .{
208- .kind = StructureError .UnexpectedType ,
209- .field_name = field_name ,
210- .field_type = @typeName (field .type ),
211- .csv_value = value ,
212- },
213- };
214- },
215- else = > {
216- @compileError (std .fmt .comptimePrint ("unsupported field type for '{}'" , .{@typeName (field .type )}));
239+ const parsed = (& self ).deserializeCsvValue (field .type , value ) catch | err | return ParseResult (table_schema ){
240+ .@"error" = .{
241+ .kind = err ,
242+ .field_name = field_name ,
243+ .field_type = @typeName (field .type ),
244+ .csv_value = value ,
217245 },
218- }
246+ };
247+ @field (out , field_name ) = parsed ;
219248 }
220249 return ParseResult (table_schema ){
221250 .ok = .{
@@ -240,7 +269,6 @@ pub fn StructuredTable(table_schema: type) type {
240269 if (row_index >= self .getRowCount ()) return TableError .RowNotFound ;
241270 inline for (schema_info .@"struct" .fields ) | field | {
242271 const field_name = field .name ;
243- const field_type = @typeInfo (field .type );
244272 const column_indexes = self .table .findColumnIndexesByValue (self .allocator , 0 , field_name ) catch return ParseResult (table_schema ){
245273 .@"error" = .{
246274 .kind = StructureError .MissingColumn ,
@@ -259,29 +287,9 @@ pub fn StructuredTable(table_schema: type) type {
259287 },
260288 };
261289 const column_index = column_indexes [0 ];
262- if (field_type == .pointer and
263- field_type .pointer .size == .slice and
264- field_type .pointer .child == u8 )
265- {
266- try self .table .replaceValue (row_index + 1 , column_index , @field (row , field_name ));
267- continue ;
268- }
269- switch (field_type ) {
270- .bool = > {
271- if (@field (row , field_name )) {
272- try self .table .replaceValue (row_index + 1 , column_index , "true" );
273- } else {
274- try self .table .replaceValue (row_index + 1 , column_index , "false" );
275- }
276- },
277- .int , .float = > {
278- const formatted = std .fmt .allocPrint (self .arena_allocator .allocator (), "{d}" , .{@field (row , field_name )}) catch return TableError .OutOfMemory ;
279- try self .table .replaceValue (row_index + 1 , column_index , formatted );
280- },
281- else = > {
282- @compileError (std .fmt .comptimePrint ("unsupported field type for '{}'" , .{@typeName (field .type )}));
283- },
284- }
290+ const table_index = headerAwareToTableIndex (row_index );
291+ const value = try self .serializeCsvValue (field .type , @field (row , field_name ));
292+ try self .table .replaceValue (table_index , column_index , value );
285293 }
286294 return ParseResult (table_schema ){
287295 .ok = .{
@@ -306,8 +314,8 @@ pub fn StructuredTable(table_schema: type) type {
306314 try self .table .replaceValue (0 , header_row_index , field .name );
307315 }
308316 }
309- const table_insert_idx = headerAwareToTableIndex (row_index );
310- const index = self .table .insertEmptyRow (table_insert_idx ) catch return TableError .OutOfMemory ;
317+ const table_index = if (row_index ) | index | headerAwareToTableIndex ( index ) else null ;
318+ const index = self .table .insertEmptyRow (table_index ) catch return TableError .OutOfMemory ;
311319 const data_index = headerAwareToDataIndex (index ) orelse return TableError .RowNotFound ;
312320 _ = try self .editRow (data_index , row );
313321 }
0 commit comments