@@ -21,7 +21,7 @@ pub const StructureError = error{
2121/// Result of parsing a row into a structured type
2222/// Used to provide detailed error information when parsing fails
2323pub fn ParseResult (table_schema : type ) type {
24- return union {
24+ return union ( enum ) {
2525 /// Successfully parsed structured value
2626 ok : struct {
2727 /// The parsed structured value
@@ -30,7 +30,7 @@ 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+ kind : ( StructureError || TableError ) ,
3434 /// The name of the field that caused the error
3535 field_name : ? []const u8 ,
3636 /// The expected type of the field that caused the error
@@ -95,11 +95,9 @@ pub fn StructuredTable(table_schema: type) type {
9595 /// Convert a data-row index to the corresponding underlying table index.
9696 ///
9797 /// 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 ;
98+ /// data rows start at 1. This helper maps a data-row index to the `Table` insert index.
99+ fn headerAwareToTableIndex (data_index : usize ) usize {
100+ return data_index + 1 ;
103101 }
104102
105103 /// Convert an underlying table index to a data-row index.
@@ -110,6 +108,85 @@ pub fn StructuredTable(table_schema: type) type {
110108 return table_index - 1 ;
111109 }
112110
111+ /// Deserialize a CSV value into the appropriate field type
112+ fn deserializeCsvValue (self : Self , comptime T : type , value : []const u8 ) (TableError || StructureError )! T {
113+ const type_info = @typeInfo (T );
114+ if (type_info == .pointer and
115+ type_info .pointer .size == .slice and
116+ type_info .pointer .child == u8 )
117+ {
118+ return value ;
119+ }
120+ switch (type_info ) {
121+ .optional = > {
122+ const child_type = type_info .optional .child ;
123+ if (value .len == 0 ) {
124+ return null ;
125+ } else {
126+ return try self .deserializeCsvValue (child_type , value );
127+ }
128+ },
129+ .bool = > {
130+ const lower = std .ascii .allocLowerString (self .allocator , value ) catch return TableError .OutOfMemory ;
131+ defer self .allocator .free (lower );
132+ for ([_ ][]const u8 { "true" , "1" , "yes" , "y" }) | true_word | {
133+ if (std .mem .eql (u8 , true_word , lower )) {
134+ return true ;
135+ }
136+ }
137+ for ([_ ][]const u8 { "false" , "0" , "no" , "n" }) | false_word | {
138+ if (std .mem .eql (u8 , false_word , lower )) {
139+ return false ;
140+ }
141+ }
142+ return StructureError .UnexpectedType ;
143+ },
144+ .int = > {
145+ return std .fmt .parseInt (T , value , 0 ) catch StructureError .UnexpectedType ;
146+ },
147+ .float = > {
148+ return std .fmt .parseFloat (T , value ) catch StructureError .UnexpectedType ;
149+ },
150+ else = > {
151+ @compileError (std .fmt .comptimePrint ("unsupported field type for '{}'" , .{@typeName (type_info )}));
152+ },
153+ }
154+ }
155+
156+ /// Serialize a field value into a CSV-compatible string
157+ fn serializeCsvValue (self : * Self , comptime T : type , value : T ) TableError ! []const u8 {
158+ const type_info = @typeInfo (T );
159+ if (type_info == .pointer and
160+ type_info .pointer .size == .slice and
161+ type_info .pointer .child == u8 )
162+ {
163+ return value ;
164+ }
165+ switch (type_info ) {
166+ .optional = > {
167+ const child_type = type_info .optional .child ;
168+ if (value == null ) {
169+ return "" ;
170+ } else {
171+ return try self .serializeCsvValue (child_type , value .? );
172+ }
173+ },
174+ .bool = > {
175+ if (value ) {
176+ return "true" ;
177+ } else {
178+ return "false" ;
179+ }
180+ },
181+ .int , .float = > {
182+ return std .fmt .allocPrint (self .arena_allocator .allocator (), "{d}" , .{value }) catch TableError .OutOfMemory ;
183+ },
184+ else = > {
185+ @compileError (std .fmt .comptimePrint ("unsupported field type for '{}'" , .{@typeName (type_info )}));
186+ },
187+ }
188+ }
189+
113190 /// Get a structured row from the StructuredTable by index
114191 ///
115192 /// Example looping through all rows:
@@ -131,7 +208,6 @@ pub fn StructuredTable(table_schema: type) type {
131208 var out : table_schema = undefined ;
132209 inline for (schema_info .@"struct" .fields ) | field | {
133210 const field_name = field .name ;
134- const field_type = @typeInfo (field .type );
135211 const column_indexes = self .table .findColumnIndexesByValue (self .allocator , 0 , field_name ) catch return ParseResult (table_schema ){
136212 .@"error" = .{
137213 .kind = StructureError .MissingColumn ,
@@ -159,63 +235,15 @@ pub fn StructuredTable(table_schema: type) type {
159235 };
160236 defer self .allocator .free (rows );
161237 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 )}));
238+ const parsed = (& self ).deserializeCsvValue (field .type , value ) catch | err | return ParseResult (table_schema ){
239+ .@"error" = .{
240+ .kind = err ,
241+ .field_name = field_name ,
242+ .field_type = @typeName (field .type ),
243+ .csv_value = value ,
217244 },
218- }
245+ };
246+ @field (out , field_name ) = parsed ;
219247 }
220248 return ParseResult (table_schema ){
221249 .ok = .{
@@ -240,7 +268,6 @@ pub fn StructuredTable(table_schema: type) type {
240268 if (row_index >= self .getRowCount ()) return TableError .RowNotFound ;
241269 inline for (schema_info .@"struct" .fields ) | field | {
242270 const field_name = field .name ;
243- const field_type = @typeInfo (field .type );
244271 const column_indexes = self .table .findColumnIndexesByValue (self .allocator , 0 , field_name ) catch return ParseResult (table_schema ){
245272 .@"error" = .{
246273 .kind = StructureError .MissingColumn ,
@@ -259,29 +286,9 @@ pub fn StructuredTable(table_schema: type) type {
259286 },
260287 };
261288 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- }
289+ const table_index = headerAwareToTableIndex (row_index );
290+ const value = try self .serializeCsvValue (field .type , @field (row , field_name ));
291+ try self .table .replaceValue (table_index , column_index , value );
285292 }
286293 return ParseResult (table_schema ){
287294 .ok = .{
@@ -306,8 +313,8 @@ pub fn StructuredTable(table_schema: type) type {
306313 try self .table .replaceValue (0 , header_row_index , field .name );
307314 }
308315 }
309- const table_insert_idx = headerAwareToTableIndex (row_index );
310- const index = self .table .insertEmptyRow (table_insert_idx ) catch return TableError .OutOfMemory ;
316+ const table_index = if (row_index ) | index | headerAwareToTableIndex ( index ) else null ;
317+ const index = self .table .insertEmptyRow (table_index ) catch return TableError .OutOfMemory ;
311318 const data_index = headerAwareToDataIndex (index ) orelse return TableError .RowNotFound ;
312319 _ = try self .editRow (data_index , row );
313320 }
0 commit comments