NewRecordBuilder constructs the scode.Bytes representation for records built from an array of input field selectors expressed as field.Path. Append should be called to enter field values in the left to right order of the provided fields and Encode is called to retrieve the nested scode.Bytes value.
(sctx *Context, fields field.List)
| 58 | // value. Reset should be called before encoding the next record. This mechanism |
| 59 | // never builds records with optional fields. |
| 60 | func NewRecordBuilder(sctx *Context, fields field.List) (*RecordBuilder, error) { |
| 61 | seenRecords := make(map[string]bool) |
| 62 | fieldInfos := make([]fieldInfo, 0, len(fields)) |
| 63 | var currentRecord []string |
| 64 | for i, field := range fields { |
| 65 | if field.IsEmpty() { |
| 66 | return nil, errors.New("empty field path") |
| 67 | } |
| 68 | names := field |
| 69 | // Grab everything except the leaf field name and see if |
| 70 | // it has changed from the previous field. If it hasn't, |
| 71 | // things are simple but if it has, we need to carefully |
| 72 | // figure out which records we are stepping in and out of. |
| 73 | record := names[:len(names)-1] |
| 74 | var containerBegins []string |
| 75 | if !slices.Equal(record, currentRecord) { |
| 76 | // currentRecord is what nested record the scode.Builder |
| 77 | // is currently working on, record is the nested |
| 78 | // record for the current field. First figure out |
| 79 | // what (if any) common parents are shared. |
| 80 | l := min(len(currentRecord), len(record)) |
| 81 | pos := 0 |
| 82 | for pos < l { |
| 83 | if record[pos] != currentRecord[pos] { |
| 84 | break |
| 85 | } |
| 86 | pos += 1 |
| 87 | } |
| 88 | |
| 89 | // Note any previously encoded records that are |
| 90 | // now finished. |
| 91 | if i > 0 { |
| 92 | fieldInfos[i-1].containerEnds = len(currentRecord) - pos |
| 93 | } |
| 94 | |
| 95 | // Validate any new records that we're starting |
| 96 | // (i.e., ensure that we didn't handle fields from |
| 97 | // the same record previously), then record the names |
| 98 | // of all these records. |
| 99 | for pos2 := pos; pos2 < len(record); pos2++ { |
| 100 | recname := strings.Join(record[:pos2+1], ".") |
| 101 | _, seen := seenRecords[recname] |
| 102 | if seen { |
| 103 | return nil, fmt.Errorf("fields in record %s must be adjacent", recname) |
| 104 | } |
| 105 | seenRecords[recname] = true |
| 106 | containerBegins = append(containerBegins, record[pos2]) |
| 107 | } |
| 108 | currentRecord = record |
| 109 | } |
| 110 | if isIn(field, fieldInfos) { |
| 111 | return nil, &DuplicateFieldError{strings.Join(field, ".")} |
| 112 | } |
| 113 | fieldInfos = append(fieldInfos, fieldInfo{field, containerBegins, 0}) |
| 114 | } |
| 115 | if len(fieldInfos) > 0 { |
| 116 | fieldInfos[len(fieldInfos)-1].containerEnds = len(currentRecord) |
| 117 | } |
no test coverage detected