Skip to content

v2.1.4: Internal package refactoring#17

Merged
mkenney merged 4 commits into
mainfrom
v2.1.4-dev
May 15, 2026
Merged

v2.1.4: Internal package refactoring#17
mkenney merged 4 commits into
mainfrom
v2.1.4-dev

Conversation

@mkenney

@mkenney mkenney commented May 15, 2026

Copy link
Copy Markdown
Member

Internal package refactoring, ToStruct/ToStructE removal, documentation corrections, flag-behavior fixes, and substantially expanded test and example coverage.

Removed

  • ToStruct[T] — public API removed. Use cast.To[T] instead; struct hydration is dispatched automatically when T is a struct type.
  • ToStructE[T] — public API removed. Use cast.ToE[T] instead. The implementation moved to internal/cast as ToStructE.
  • README references to cast.ToStruct[T] / cast.ToStructE[T] replaced with cast.To[T] / cast.ToE[T].

Changed

internal/cast/ package

Implementation moved out of the public package surface:

  • to.go and to.type.go remain in package cast and expose To, ToE, and the public type API.
  • All converter sources (to.bool.go, to.int.go, to.float.go, to.complex.go, to.string.go, to.slice.go, to.map.go, to.chan.go, to.func.go, to.struct.go, to.time.go, to.duration.go, to.big.go, to.net.go, to.url.go, to.regexp.go, util.decode.go, util.reflect.go) moved to internal/cast/.
  • All *_test.go files moved to internal/cast/ except test.examples_test.go, which stays in main to validate the public API surface via godoc examples.
  • Non-generic public types (Flag, Op, flag constants, error vars) are defined in the internal package; main package re-exports them via Go type aliases. Generic types (Func, Types, Tbase, Tslice, Tchan, Tmap) stay in main because Go 1.21 does not support generic type aliases.
  • Internal ops struct renamed to Ops with exported fields so the main package can construct it via internal.ParseOps.
  • makeChan/makeFunc rewritten to use reflection-based casting (CastToType) instead of recursively calling the public ToE, breaking the otherwise-circular import path between main and internal.
  • ToChan and ToFunc are non-generic in the internal package; main's ToE performs the final type assertion (with reflect.Convert fallback) to produce the user's named chan T or Func[T].

Options documentation

  • Added FORMAT row to the README Options table (previously absent).
  • Added Type and Scope columns to the Options table; reordered rows to match the constant declaration order.
  • Added a "Global flag propagation" subsection below the table with runnable examples showing ABS, JSON, LENGTH, UNIQUE_VALUES, and FORMAT propagating into nested casts.
  • All flag constants in internal/cast/to.type.go now carry full multi-line doc comments stating their value type, scope, applicability, and propagation behavior.

Example function naming convention

Happy-path ExampleToE_* functions that never showed an error have been renamed to ExampleTo_* and switched to cast.To; the freed-up ExampleToE_* names now hold corresponding error examples. Every ExampleTo_* has a matching ExampleToE_* error example. Total example functions: 73.

Added

Test coverage for the conversion table

33 new sub-tests cover previously-untested cells of the conversion table: scalar → map errors, scalar → struct errors, slice/map/struct → bool|complex errors, map → int/uint/float errors, struct → uint error, and the documented []byte/[]runemap[K]V success path. New ABS coverage added for negative float64 sources and negative-float strings to uint targets.

Godoc example functions

24 new example functions covering previously-undocumented type categories: time.Time (string, int, float, FORMAT), time.Duration (string, int), net.IP (string, packed uint32), *url.URL, *regexp.Regexp, *big.Int (decimal, hex), *big.Float, bool, complex64/complex128, error and fmt.Stringer interface targets, DECODE (scalar and slice), []bytestring, JSON-string → map, nested chan []int and Func[chan int].

Fixed

time.Time*big.Int round-trip

time.Time → *big.Int now uses val.UnixNano() (was val.Unix()). *big.Int → time.Time already used nanoseconds, so the round-trip is now lossless for times within int64 range. Three test cases in TestTimeToBigConversions were updated accordingly.

Documentation conflicts

  • Named-type table footnote ᵗ said *big.Int → time.Time was Unix seconds; corrected to nanoseconds. *big.Float → time.Time remains documented as Unix seconds with fractional precision.
  • Supported Conversions table cell for string → map[K]V was , but JSON-object/array strings auto-decode; corrected to ~ⁿ with a new footnote.
  • Note ⁴ for string → bool said only strconv.ParseBool variants are accepted; the integer-parse fallback path was undocumented and is now described ("-1" → true, "0.1" → false because floor(0.1) = 0, etc.).
  • Note ᵈ and the named-type example for int*time.Time claimed Unix seconds; corrected to nanoseconds. The toTime docstring previously said "Unix seconds" for integer sources; fixed to "Unix nanoseconds".

Flag documentation

  • FORMAT constant comment said "time/duration parsing"; only time.Time reads ops.FormatVal. Corrected.
  • PRIVATE constant comment said "include unexported struct fields in map output" — too narrow. Corrected to describe both directions (struct→map source-field reading and map/struct→struct target hydration).
  • README ABS row said "negative signed inputs"; ABS also applies to negative float32/float64 sources and numeric strings that parse to negative floats. Description, example block, and IMPORTANT callout updated.

parseTimeString undocumented edge case

When FORMAT is not set, parseTimeString falls through to time.Parse("", str) after the 19-format loop. The empty string "" is a valid source that returns time.Time{} because time.Parse("", "") succeeds. Now documented in both the function godoc and the README footnote ᶠ.

Wrong-typed DEFAULT errors immediately

Every converter type-asserts the DEFAULT value at the top, before inspecting the input. Passing a wrong type returns an error immediately even for inputs that would otherwise convert successfully. Now documented on the Op struct, the DEFAULT constant, and the README Options table.

Documentation (README.md)

  • IMPORTANT callout updated to describe the actual string → bool two-step parse behavior and to mention float-source applicability for ABS.
  • Named-type code example updated to use a meaningful Unix-nanosecond value (int64(1_713_787_200_000_000_000) for 2024-04-22) instead of a value that only made sense as Unix seconds.
  • The struct-hydration example block under #### Structs rewritten to use cast.To / cast.ToE after the removal of ToStruct / ToStructE.

Copilot AI review requested due to automatic review settings May 15, 2026 02:45
@mkenney mkenney marked this pull request as ready for review May 15, 2026 02:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR (v2.1.4) refactors the cast package by relocating the conversion implementation behind an internal/cast/ boundary while keeping To / ToE and the public type aliases in the root package. It removes the now-redundant ToStruct / ToStructE public APIs (callers use To / ToE for struct targets), fixes the time.Time*big.Int round-trip to use UnixNano(), corrects several documentation conflicts (Options table, footnotes for time/bigInt, string→bool parse semantics, string→map JSON-decode path), and substantially expands tests and runnable godoc examples (24 new examples, 33 new sub-tests covering previously-untested error cells of the conversion table).

Changes:

  • Move all converter sources and tests into internal/cast/; root to.go / to.type.go re-export non-generic types via Go aliases and dispatch to internal.* (with makeChan / makeFunc rewritten on top of CastToType to break the import cycle).
  • Drop ToStruct[T] / ToStructE[T] from the public API; rename happy-path examples to ExampleTo_* and reserve ExampleToE_* for matching error examples.
  • Fix time.Time → *big.Int to use UnixNano(), expand flag/option docs (FORMAT row, propagation section, multi-line constant comments), and document the wrong-typed DEFAULT early-error behavior plus the empty-string parseTimeString edge case.

Reviewed changes

Copilot reviewed 42 out of 64 changed files in this pull request and generated no comments.

Show a summary per file
File Description
to.go Dispatch switch routes to internal.* converters; pointer/func/chan paths use CastToType and final assertion in main.
to.type.go Reduced to type aliases and constant re-exports of the new internal/cast definitions.
to.func.go Removed; replaced by internal/cast/to.func.go reflection-based implementation.
internal/cast/to.type.go New canonical home for Flag, Op, flag constants (with multi-line docs), Ops, ParseOps, error sentinels.
internal/cast/to.func.go, to.chan.go Rewritten to use CastToType / reflect.MakeFunc / reflect.MakeChan, eliminating the per-element generic switch.
internal/cast/to.big.go time.Time → *big.Int now uses UnixNano() for lossless round-trip.
internal/cast/util.reflect.go, util.decode.go Exported NamedConverters, IsScalarKind, CastToType, decode helpers.
internal/cast/to.{bool,int,float,complex,string,slice,map,struct,time,duration,net,url,regexp}.go Moved/renamed (lowercase → exported) converters; behavior preserved.
internal/cast/*_test.go, to.types_test.go, test.options_test.go, test.panic_test.go Tests relocated to internal package; new error/edge-case sub-tests added.
internal/cast/my._test.go New TestMyTest probe-style test with non-conventional filename.
test.examples_test.go Stays in root; renamed happy-path examples to ExampleTo_*, added 24 new examples and matching ExampleToE_* error variants.
README.md New Options table columns, FORMAT row, global-propagation subsection, footnote/IMPORTANT-callout corrections, struct-hydration example uses cast.To.
CHANGELOG.md Adds v2.1.4 entry summarizing the refactor and fixes.
Comments suppressed due to low confidence (1)

internal/cast/my._test.go:33

  • This new test file appears to be developer scratch/probe code that was accidentally committed. The filename my._test.go and the test name TestMyTest are not descriptive and do not follow the naming conventions used by every other test file in this directory (to.<type>_test.go with Test<Behavior> names). Consider either deleting this file or, if the anonymous-struct hydration coverage is valuable, moving the assertion into to.struct_test.go under a properly named test.
package cast_test

import (
	"testing"

	"github.com/bdlm/cast/v2"
)

func TestMyTest(t *testing.T) {
	t.Run("Casting to anonymous struct", func(t *testing.T) {
		result, err := cast.ToE[struct {
			Foo string
			Bar int
		}](map[string]any{
			"Foo": "hello",
			"Bar": 42,
		})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if result != struct {
			Foo string
			Bar int
		}{
			Foo: "hello",
			Bar: 42,
		} {
			t.Errorf("unexpected result: %v", result)
		}
		t.Logf("successfully cast to anonymous struct: %+v", result)
	})
}


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mkenney mkenney merged commit 09dd142 into main May 15, 2026
6 checks passed
@mkenney mkenney deleted the v2.1.4-dev branch May 15, 2026 02:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants