(t reflect.Type, seen map[reflect.Type]bool, atomName string, parentName string)
| 151 | } |
| 152 | |
| 153 | func validateAtomTypeRecursive(t reflect.Type, seen map[reflect.Type]bool, atomName string, parentName string) error { |
| 154 | if t == nil { |
| 155 | return makeAtomError(atomName, parentName, "nil type") |
| 156 | } |
| 157 | |
| 158 | if seen[t] { |
| 159 | return nil |
| 160 | } |
| 161 | seen[t] = true |
| 162 | |
| 163 | // Check if type implements json.Marshaler or encoding.TextMarshaler |
| 164 | if implementsJSON(t) { |
| 165 | return nil |
| 166 | } |
| 167 | |
| 168 | // Allow time.Time explicitly |
| 169 | if t == timeType { |
| 170 | return nil |
| 171 | } |
| 172 | |
| 173 | switch t.Kind() { |
| 174 | case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, |
| 175 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, |
| 176 | reflect.Float32, reflect.Float64, reflect.String: |
| 177 | return nil |
| 178 | |
| 179 | case reflect.Ptr: |
| 180 | return validateAtomTypeRecursive(t.Elem(), seen, atomName, parentName) |
| 181 | |
| 182 | case reflect.Array, reflect.Slice: |
| 183 | elemType := t.Elem() |
| 184 | // Allow []any as a JSON value slot |
| 185 | if elemType.Kind() == reflect.Interface && elemType.NumMethod() == 0 { |
| 186 | return nil |
| 187 | } |
| 188 | return validateAtomTypeRecursive(elemType, seen, atomName, parentName) |
| 189 | |
| 190 | case reflect.Map: |
| 191 | if t.Key().Kind() != reflect.String { |
| 192 | return makeAtomError(atomName, parentName, fmt.Sprintf("map key must be string, got %s", t.Key().Kind())) |
| 193 | } |
| 194 | elemType := t.Elem() |
| 195 | // Allow map[string]any as a JSON value slot |
| 196 | if elemType.Kind() == reflect.Interface && elemType.NumMethod() == 0 { |
| 197 | return nil |
| 198 | } |
| 199 | return validateAtomTypeRecursive(elemType, seen, atomName, parentName) |
| 200 | |
| 201 | case reflect.Struct: |
| 202 | structName := t.Name() |
| 203 | if structName == "" { |
| 204 | structName = "anonymous struct" |
| 205 | } |
| 206 | for i := 0; i < t.NumField(); i++ { |
| 207 | field := t.Field(i) |
| 208 | fieldPath := fmt.Sprintf("%s.%s", structName, field.Name) |
| 209 | |
| 210 | if !field.IsExported() { |
no test coverage detected