pax_global_header 0000666 0000000 0000000 00000000064 14764332721 0014523 g ustar 00root root 0000000 0000000 52 comment=b2329de595e3af90fd9bab525234a595911fbce8
go-safecast-1.6.0/ 0000775 0000000 0000000 00000000000 14764332721 0013723 5 ustar 00root root 0000000 0000000 go-safecast-1.6.0/.editorconfig 0000664 0000000 0000000 00000001012 14764332721 0016372 0 ustar 00root root 0000000 0000000 # More information about this file
# https://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
[*.md]
tab_width = 2
trim_trailing_whitespace = false # can be used for indentation with double spaces
# Not enforced, markdownlint already reports it
#insert_final_newline = true
[*.{yml,yaml}]
tab_width = 2
trim_trailing_whitespace = true
# Not enforced, markdownlint already reports it
#insert_final_newline = true
[*.toml,.ini]
tab_width = 2
trim_trailing_whitespace = true
insert_final_newline = true
go-safecast-1.6.0/.gitattributes 0000664 0000000 0000000 00000000316 14764332721 0016616 0 ustar 00root root 0000000 0000000 # ensure that line endings for Windows builds are properly formatted
# see https://github.com/golangci/golangci-lint-action?tab=readme-ov-file#how-to-use
# at "Multiple OS Example" section
*.go text eol=lf
go-safecast-1.6.0/.github/ 0000775 0000000 0000000 00000000000 14764332721 0015263 5 ustar 00root root 0000000 0000000 go-safecast-1.6.0/.github/dependabot.yml 0000664 0000000 0000000 00000000736 14764332721 0020121 0 ustar 00root root 0000000 0000000 ---
version: 2
updates:
# Maintain dependencies for GitHub Actions
# These would open PR, these PR would be tested with the CI
# They will have to be merged manually by a maintainer
- package-ecosystem: github-actions
directory: /
open-pull-requests-limit: 10 # avoid spam, if no one reacts
schedule:
interval: weekly
time: '11:00'
groups:
all:
patterns:
- "*" # Group all updates into a single larger pull request.
go-safecast-1.6.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14764332721 0017320 5 ustar 00root root 0000000 0000000 go-safecast-1.6.0/.github/workflows/files.yml 0000664 0000000 0000000 00000002213 14764332721 0021143 0 ustar 00root root 0000000 0000000 ---
name: Files Lint
on:
pull_request:
push:
branches:
- main
permissions:
contents: read # for actions/checkout to fetch code
jobs:
action-lint:
runs-on: ubuntu-latest
steps:
- name: checkout-action
uses: actions/checkout@v4.2.2
- name: actionlint
uses: raven-actions/actionlint@v2.0.0
yamllint:
runs-on: ubuntu-latest
steps:
- name: checkout-action
uses: actions/checkout@v4.2.2
- name: yamllint
uses: ibiqlik/action-yamllint@v3
typos:
runs-on: ubuntu-latest
steps:
- name: checkout-action
uses: actions/checkout@v4.2.2
- name: typos-action
uses: crate-ci/typos@v1.30.2
markdownlint:
runs-on: ubuntu-latest
steps:
- name: checkout-action
uses: actions/checkout@v4.2.2
- name: markdownlint-cli2-action
uses: DavidAnson/markdownlint-cli2-action@v19.1.0
editorconfig:
runs-on: ubuntu-latest
steps:
- name: checkout-action
uses: actions/checkout@v4.2.2
- name: editorconfig-checker-action
uses: editorconfig-checker/action-editorconfig-checker@v2
go-safecast-1.6.0/.github/workflows/go.yml 0000664 0000000 0000000 00000004347 14764332721 0020460 0 ustar 00root root 0000000 0000000 ---
name: Go
on:
pull_request:
push:
branches:
- main
jobs:
go-test-lint:
name: Test & Lint
strategy:
fail-fast: false # continue to run all jobs even if one fails
matrix:
include:
- {os: ubuntu-latest, GOOS: linux, GOARCH: amd64, go-version: stable}
- {os: ubuntu-latest, GOOS: linux, GOARCH: amd64, go-version: oldstable}
- {os: ubuntu-latest, GOOS: linux, GOARCH: 386, go-version: stable}
- {os: macos-latest, GOOS: darwin, GOARCH: amd64, go-version: stable}
- {os: macos-latest, GOOS: darwin, GOARCH: arm64, go-version: stable}
- {os: windows-latest, GOOS: windows, GOARCH: amd64, go-version: stable}
- {os: windows-latest, GOOS: windows, GOARCH: 386, go-version: stable}
permissions:
contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
runs-on: ${{ matrix.os }}
env:
GOARCH: ${{ matrix.GOARCH }}
GOOS: ${{ matrix.GOOS }}
steps:
- name: checkout-action
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest
- name: Run tests
run: go test
go-coverage:
name: Code Coverage
# This one is limited to one arch as we don't need to report coverage multiple times
permissions:
contents: read # for actions/checkout to fetch code
runs-on: ubuntu-latest
steps:
- name: checkout-action
uses: actions/checkout@v4.2.2
- name: Set up Go
uses: actions/setup-go@v5
- name: Run tests
run: go test -coverprofile=coverage.txt
- name: Upload results to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Code Climate Coverage Action
uses: paambaati/codeclimate-action@v9
env:
CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}}
with:
coverageLocations: coverage.txt:gocov
prefix: github.com/ccoveille/go-safecast
go-safecast-1.6.0/.golangci.yml 0000664 0000000 0000000 00000013024 14764332721 0016307 0 ustar 00root root 0000000 0000000 ---
# golangci-lint configuration file made by @ccoVeille
# Source: https://github.com/ccoVeille/golangci-lint-config-examples/
# Author: @ccoVeille
# License: MIT
# Variant: 03-safe
# Version: v1.0.0
#
linters:
# some linters are enabled by default
# https://golangci-lint.run/usage/linters/
#
# enable some extra linters
enable:
# Errcheck is a program for checking for unchecked errors in Go code.
- errcheck
# Linter for Go source code that specializes in simplifying code.
- gosimple
# Vet examines Go source code and reports suspicious constructs.
- govet
# gosec is a security linter for Go code.
- gosec
# Detects when assignments to existing variables are not used.
- ineffassign
# It's a set of rules from staticcheck. See https://staticcheck.io/
- staticcheck
# Fast, configurable, extensible, flexible, and beautiful linter for Go.
# Drop-in replacement of golint.
- revive
# check imports order and makes it always deterministic.
- gci
# make sure to use t.Helper() when needed
- thelper
# checks if package imports are in a list of acceptable packages.
- depguard
# mirror suggests rewrites to avoid unnecessary []byte/string conversion
- mirror
# detect the possibility to use variables/constants from the Go standard library.
- usestdlibvars
# Finds commonly misspelled English words.
- misspell
# Checks for duplicate words in the source code.
- dupword
linters-settings:
gci: # define the section orders for imports
sections:
# Standard section: captures all standard packages.
- standard
# Default section: catchall that is not standard or custom
- default
# linters that related to local tool, so they should be separated
- localmodule
depguard:
rules:
# enforce the library has no dependencies except the standard library
code:
files:
- "!$test" # depguard alias for all files except the test ones
allow:
- "$gostd" # depguard alias for all standard libraries
# enforce the test files have no dependencies except the standard library and the library itself
test:
files:
- "$test" # depguard alias for all the test files
allow:
- "$gostd" # depguard alias for all standard libraries
- "github.com/ccoveille/go-safecast"
revive:
rules:
# these are the default revive rules
# you can remove the whole "rules" node if you want
# BUT
# ! /!\ they all need to be present when you want to add more rules than the default ones
# otherwise, you won't have the default rules, but only the ones you define in the "rules" node
# Blank import should be only in a main or test package, or have a comment justifying it.
- name: blank-imports
# context.Context() should be the first parameter of a function when provided as argument.
- name: context-as-argument
arguments:
- allowTypesBefore: "*testing.T"
# Basic types should not be used as a key in `context.WithValue`
- name: context-keys-type
# Importing with `.` makes the programs much harder to understand
- name: dot-imports
# Empty blocks make code less readable and could be a symptom of a bug or unfinished refactoring.
- name: empty-block
# for better readability, variables of type `error` must be named with the prefix `err`.
- name: error-naming
# for better readability, the errors should be last in the list of returned values by a function.
- name: error-return
# for better readability, error messages should not be capitalized or end with punctuation or a newline.
- name: error-strings
# report when replacing `errors.New(fmt.Sprintf())` with `fmt.Errorf()` is possible
- name: errorf
# enforces conventions on source file names.
- name: filename-format
# incrementing an integer variable by 1 is recommended to be done using the `++` operator
- name: increment-decrement
# highlights redundant else-blocks that can be eliminated from the code
- name: indent-error-flow
# This rule suggests a shorter way of writing ranges that do not use the second value.
- name: range
# receiver names in a method should reflect the struct name (p for Person, for example)
- name: receiver-naming
# redefining built in names (true, false, append, make) can lead to bugs very difficult to detect.
- name: redefines-builtin-id
# redundant else-blocks that can be eliminated from the code.
- name: superfluous-else
# prevent confusing name for variables when using `time` package
- name: time-naming
# warns when an exported function or method returns a value of an un-exported type.
- name: unexported-return
# spots and proposes to remove unreachable code. also helps to spot errors
- name: unreachable-code
# Functions or methods with unused parameters can be a symptom of an unfinished refactoring or a bug.
- name: unused-parameter
# report when a variable declaration can be simplified
- name: var-declaration
# warns when initialism, variable or package naming conventions are not followed.
- name: var-naming
misspell:
# Correct spellings using locale preferences for US or UK.
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
# Default ("") is to use a neutral variety of English.
locale: US
go-safecast-1.6.0/.ls-lint.yml 0000664 0000000 0000000 00000001061 14764332721 0016104 0 ustar 00root root 0000000 0000000 ---
# ls-lint configuration file. More information on the file format can be found on https://ls-lint.org/
ls:
.md: screamingsnakecase # README.md or CODE_OF_CONDUCT.md
# DEACTIVATED: Go files should are linted via golangci-lint revive filename-format rule
# .go: snakecase
ignore:
# .git folder cannot be linted
- .git
# .github folder contains configuration files with specific name, and should not be linted
- .github
# dot files are usually configuration files with specific name
- .ls-lint.yml
- .markdownlint.yml
- .yamllint.yml
go-safecast-1.6.0/.markdownlint.yml 0000664 0000000 0000000 00000000356 14764332721 0017241 0 ustar 00root root 0000000 0000000 ---
# Default state for all rules
default: true
MD013:
# Overload the default value (120)
line_length: 200
MD033:
allowed_elements:
#
are useful for spoilers
- summary
- details
go-safecast-1.6.0/.yamllint.yml 0000664 0000000 0000000 00000000523 14764332721 0016355 0 ustar 00root root 0000000 0000000 ---
extends: default
ignore:
# These files are imported by vale from an external repository
.vale/styles/RedHat/
rules:
line-length:
max: 200
allow-non-breakable-words: true
allow-non-breakable-inline-mappings: true
truthy:
# the node "on:" present in each GitHub Actions workflow file
ignore: |
.github/
go-safecast-1.6.0/LICENSE 0000664 0000000 0000000 00000002052 14764332721 0014727 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2024 ccoVeille
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
go-safecast-1.6.0/README.md 0000664 0000000 0000000 00000011127 14764332721 0015204 0 ustar 00root root 0000000 0000000 # 🪄 go-safecast: safe numbers conversion
[](https://goreportcard.com/report/github.com/ccoveille/go-safecast)
[](https://godoc.org/github.com/ccoVeille/go-safecast)
[](https://codecov.io/gh/ccoVeille/go-safecast)
[](https://codeclimate.com/github/ccoVeille/go-safecast)
[](https://github.com/search?q=%22%5C%22github.com%2Fccoveille%2Fgo-safecast%5C%22%22+language%3Ago++-is%3Afork+-is%3Aarchived+&type=code)

go-safecast solves the type conversion issues in Go
In Go, integer type conversion can lead to a silent and unexpected behavior and errors if not handled carefully.
This package helps to convert any number to another, and report an error when if there would be a [loss or overflow in the conversion](#conversion-issues)
## Usage
```go
package main
import (
"fmt"
"math"
"github.com/ccoveille/go-safecast"
)
func main() {
var a int
a = 42
b, err := safecast.ToUint8(a) // everything is fine
if err != nil {
fmt.Println(err)
}
fmt.Println(b)
// Output: 42
a = 255 + 1
_, err = safecast.ToUint8(a) // 256 is greater than uint8 maximum value
if err != nil {
fmt.Println(err)
// Output: conversion issue: 256 (int) is greater than 255 (uint8): maximum value for this type exceeded
}
a = -1
_, err = safecast.ToUint8(a) // -1 cannot fit in uint8
if err != nil {
fmt.Println(err)
// Output: conversion issue: -1 (int) is less than 0 (uint8): minimum value for this type exceeded
}
str := "\x99" // ASCII code 153 for Trademark symbol
e := str[0]
_, err = safecast.ToInt8(e)
if err != nil {
fmt.Println(err)
// Output: conversion issue: 153 (uint8) is greater than 127 (int8): maximum value for this type exceeded
}
}
```
[Go Playground](https://go.dev/play/p/nelJshulOnj)
## Conversion issues
Issues can happen when converting between signed and unsigned integers, or when converting to a smaller integer type.
```go
package main
import "fmt"
func main() {
var a int64
a = 42
b := uint8(a)
fmt.Println(b) // 42
a = 255 // this is the math.MaxUint8
b = uint8(a)
fmt.Println(b) // 255
a = 255 + 1
b = uint8(a)
fmt.Println(b) // 0 conversion overflow
a = -1
b = uint8(a)
fmt.Println(b) // 255 conversion overflow
}
```
[Go Playground](https://go.dev/play/p/DHfNUcZBvVn)
So you need to adapt your code to write something like this.
```go
package main
import "fmt"
func main() {
var a int64
a = 42
if a < 0 || a > math.MaxUint8 {
log.Println("overflow") // Output: overflow
}
fmt.Println(b) // 42
a = 255 // this is the math.MaxUint8
b = uint8(a)
fmt.Println(b) // 255
a = 255 + 1
b = uint8(a)
if a < 0 || a > math.MaxUint8 {
log.Println("overflow") // Output: overflow
}
fmt.Println(b) // Output: 0
a = -1
b = uint8(a)
if a < 0 || a > math.MaxUint8 {
log.Println("overflow") // Output: overflow
}
fmt.Println(b) // Output:255
}
```
[Go Playground](https://go.dev/play/p/qAHGyy4NCLP)
`go-safecast` is there to avoid boilerplate copy pasta.
## Motivation
The gosec project raised this to my attention when the gosec [G115 rule was added](https://github.com/securego/gosec/pull/1149)
> G115: Potential overflow when converting between integer types.
This issue was way more complex than expected, and required multiple fixes.
[CWE-190](https://cwe.mitre.org/data/definitions/190.html) explains in detail.
But to sum it up, you can face:
- infinite loop
- access to wrong resource by id
- grant access to someone who exhausted their quota
The gosec G115 will now report issues in a lot of project.
## Alternatives
Some libraries existed, but they were not able to cover all the use cases.
- [github.com/rung/go-safecast](https://github.com/rung/go-safecast):
Unmaintained, not architecture agnostic, do not support `uint` -> `int` conversion
- [github.com/cybergarage/go-safecast](https://github.com/cybergarage/go-safecast)
Work with pointer like `json.Marshall`
## Stargazers over time
[](https://starchart.cc/ccoVeille/go-safecast)
go-safecast-1.6.0/asserters.go 0000664 0000000 0000000 00000002666 14764332721 0016277 0 ustar 00root root 0000000 0000000 package safecast
import "math"
func negative[T Number](t T) bool {
return t < 0
}
func sameSign[T1, T2 Number](a T1, b T2) bool {
return negative(a) == negative(b)
}
func getUpperBoundary(value any) any {
var upper any = math.Inf(1)
switch value.(type) {
case int8:
upper = int8(math.MaxInt8)
case int16:
upper = int16(math.MaxInt16)
case int32:
upper = int32(math.MaxInt32)
case int64:
upper = int64(math.MaxInt64)
case int:
upper = int(math.MaxInt)
case uint8:
upper = uint8(math.MaxUint8)
case uint32:
upper = uint32(math.MaxUint32)
case uint16:
upper = uint16(math.MaxUint16)
case uint64:
upper = uint64(math.MaxUint64)
case uint:
upper = uint(math.MaxUint)
// Note: there is no float64 boundary
// because float64 cannot overflow
case float32:
upper = float32(math.MaxFloat32)
}
return upper
}
func getLowerBoundary(value any) any {
var lower any = math.Inf(-1)
switch value.(type) {
case int64:
lower = int64(math.MinInt64)
case int32:
lower = int32(math.MinInt32)
case int16:
lower = int16(math.MinInt16)
case int8:
lower = int8(math.MinInt8)
case int:
lower = int(math.MinInt)
case uint:
lower = uint(0)
case uint8:
lower = uint8(0)
case uint16:
lower = uint16(0)
case uint32:
lower = uint32(0)
case uint64:
lower = uint64(0)
// Note: there is no float64 boundary
// because float64 cannot overflow
case float32:
lower = float32(-math.MaxFloat32)
}
return lower
}
go-safecast-1.6.0/asserters_test.go 0000664 0000000 0000000 00000022045 14764332721 0017327 0 ustar 00root root 0000000 0000000 package safecast_test
import (
"errors"
"math"
"strings"
"testing"
"github.com/ccoveille/go-safecast"
)
func assertEqual[V comparable](t *testing.T, expected, got V) {
t.Helper()
if expected == got {
return
}
t.Errorf("Not equal: \n"+
"expected: %v (%T)\n"+
"actual : %v (%T)", expected, expected, got, got)
}
func requireError(t *testing.T, err error) {
t.Helper()
if err == nil {
t.Fatal("expected error")
}
}
func requireErrorIs(t *testing.T, err error, expected error) {
t.Helper()
requireError(t, err)
if !errors.Is(err, expected) {
t.Fatalf("unexpected error got %v, expected %v", err, expected)
}
}
func requireErrorContains(t *testing.T, err error, text string) {
t.Helper()
requireErrorIs(t, err, safecast.ErrConversionIssue)
errMessage := err.Error()
if !strings.Contains(errMessage, text) {
t.Fatalf("error message should contain %q: %q", text, errMessage)
}
}
func assertNoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Errorf("expected no error, got %v", err)
}
}
type caseInt8[in safecast.Number] struct {
name string
input in
want int8
}
func assertInt8OK[in safecast.Number](t *testing.T, tests []caseInt8[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToInt8(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
func assertInt8Error[in safecast.Number](t *testing.T, tests []caseInt8[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToInt8(tt.input)
requireErrorIs(t, err, safecast.ErrConversionIssue)
assertEqual(t, tt.want, got)
})
}
}
func TestErrorMessage(t *testing.T) {
_, err := safecast.ToUint8(-1)
requireErrorIs(t, err, safecast.ErrConversionIssue)
requireErrorIs(t, err, safecast.ErrExceedMinimumValue)
requireErrorContains(t, err, "than 0 (uint8)")
_, err = safecast.ToUint8(math.MaxInt16)
requireErrorIs(t, err, safecast.ErrConversionIssue)
requireErrorIs(t, err, safecast.ErrExceedMaximumValue)
requireErrorContains(t, err, "than 255 (uint8)")
_, err = safecast.ToInt8(-math.MaxInt16)
requireErrorIs(t, err, safecast.ErrConversionIssue)
requireErrorIs(t, err, safecast.ErrExceedMinimumValue)
requireErrorContains(t, err, "than -128 (int8)")
}
type caseUint8[in safecast.Number] struct {
name string
input in
want uint8
}
func assertUint8OK[in safecast.Number](t *testing.T, tests []caseUint8[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToUint8(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
func assertUint8Error[in safecast.Number](t *testing.T, tests []caseUint8[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Helper()
got, err := safecast.ToUint8(tt.input)
requireErrorIs(t, err, safecast.ErrConversionIssue)
assertEqual(t, tt.want, got)
})
}
}
type caseInt16[in safecast.Number] struct {
name string
input in
want int16
}
func assertInt16OK[in safecast.Number](t *testing.T, tests []caseInt16[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToInt16(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
func assertInt16Error[in safecast.Number](t *testing.T, tests []caseInt16[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToInt16(tt.input)
requireErrorIs(t, err, safecast.ErrConversionIssue)
assertEqual(t, tt.want, got)
})
}
}
type caseUint16[in safecast.Number] struct {
name string
input in
want uint16
}
func assertUint16OK[in safecast.Number](t *testing.T, tests []caseUint16[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToUint16(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
func assertUint16Error[in safecast.Number](t *testing.T, tests []caseUint16[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Helper()
got, err := safecast.ToUint16(tt.input)
requireErrorIs(t, err, safecast.ErrConversionIssue)
assertEqual(t, tt.want, got)
})
}
}
type caseInt32[in safecast.Number] struct {
name string
input in
want int32
}
func assertInt32OK[in safecast.Number](t *testing.T, tests []caseInt32[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToInt32(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
func assertInt32Error[in safecast.Number](t *testing.T, tests []caseInt32[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToInt32(tt.input)
requireErrorIs(t, err, safecast.ErrConversionIssue)
assertEqual(t, tt.want, got)
})
}
}
type caseUint32[in safecast.Number] struct {
name string
input in
want uint32
}
func assertUint32OK[in safecast.Number](t *testing.T, tests []caseUint32[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToUint32(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
func assertUint32Error[in safecast.Number](t *testing.T, tests []caseUint32[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Helper()
got, err := safecast.ToUint32(tt.input)
requireErrorIs(t, err, safecast.ErrConversionIssue)
assertEqual(t, tt.want, got)
})
}
}
type caseInt64[in safecast.Number] struct {
name string
input in
want int64
}
func assertInt64OK[in safecast.Number](t *testing.T, tests []caseInt64[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToInt64(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
func assertInt64Error[in safecast.Number](t *testing.T, tests []caseInt64[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToInt64(tt.input)
requireErrorIs(t, err, safecast.ErrConversionIssue)
assertEqual(t, tt.want, got)
})
}
}
type caseUint64[in safecast.Number] struct {
name string
input in
want uint64
}
func assertUint64OK[in safecast.Number](t *testing.T, tests []caseUint64[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToUint64(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
func assertUint64Error[in safecast.Number](t *testing.T, tests []caseUint64[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToUint64(tt.input)
requireErrorIs(t, err, safecast.ErrConversionIssue)
assertEqual(t, tt.want, got)
})
}
}
type caseInt[in safecast.Number] struct {
name string
input in
want int
}
func assertIntOK[in safecast.Number](t *testing.T, tests []caseInt[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToInt(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
func assertIntError[in safecast.Number](t *testing.T, tests []caseInt[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToInt(tt.input)
requireErrorIs(t, err, safecast.ErrConversionIssue)
assertEqual(t, tt.want, got)
})
}
}
type caseUint[in safecast.Number] struct {
name string
input in
want uint
}
func assertUintOK[in safecast.Number](t *testing.T, tests []caseUint[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToUint(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
func assertUintError[in safecast.Number](t *testing.T, tests []caseUint[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToUint(tt.input)
requireErrorIs(t, err, safecast.ErrConversionIssue)
assertEqual(t, tt.want, got)
})
}
}
type caseFloat32[in safecast.Number] struct {
name string
input in
want float32
}
func assertFloat32OK[in safecast.Number](t *testing.T, tests []caseFloat32[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToFloat32(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
func assertFloat32Error[in safecast.Number](t *testing.T, tests []caseFloat32[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToFloat32(tt.input)
requireErrorIs(t, err, safecast.ErrConversionIssue)
assertEqual(t, tt.want, got)
})
}
}
type caseFloat64[in safecast.Number] struct {
name string
input in
want float64
}
func assertFloat64OK[in safecast.Number](t *testing.T, tests []caseFloat64[in]) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := safecast.ToFloat64(tt.input)
assertNoError(t, err)
assertEqual(t, tt.want, got)
})
}
}
go-safecast-1.6.0/conversion.go 0000664 0000000 0000000 00000021345 14764332721 0016444 0 ustar 00root root 0000000 0000000 package safecast
import (
"errors"
"fmt"
"math"
"reflect"
"strconv"
"strings"
)
// Convert attempts to convert any value to the desired type
// - If the conversion is possible, the converted value is returned.
// - If the conversion results in a value outside the range of the desired type, an [ErrRangeOverflow] error is wrapped in the returned error.
// - If the conversion exceeds the maximum value of the desired type, an [ErrExceedMaximumValue] error is wrapped in the returned error.
// - If the conversion exceeds the minimum value of the desired type, an [ErrExceedMinimumValue] error is wrapped in the returned error.
// - If the conversion is not possible for the desired type, an [ErrUnsupportedConversion] error is wrapped in the returned error.
// - If the conversion fails from string, an [ErrStringConversion] error is wrapped in the returned error.
// - If the conversion results in an error, an [ErrConversionIssue] error is wrapped in the returned error.
func Convert[NumOut Number, NumIn Input](orig NumIn) (converted NumOut, err error) {
v := reflect.ValueOf(orig)
switch v.Kind() {
case reflect.Int:
return convertFromNumber[NumOut](int(v.Int()))
case reflect.Uint:
return convertFromNumber[NumOut](uint(v.Uint()))
case reflect.Int8:
//nolint:gosec // the int8 is confirmed
return convertFromNumber[NumOut](int8(v.Int()))
case reflect.Uint8:
//nolint:gosec // the uint8 is confirmed
return convertFromNumber[NumOut](uint8(v.Uint()))
case reflect.Int16:
//nolint:gosec // the int16 is confirmed
return convertFromNumber[NumOut](int16(v.Int()))
case reflect.Uint16:
//nolint:gosec // the uint16 is confirmed
return convertFromNumber[NumOut](uint16(v.Uint()))
case reflect.Int32:
//nolint:gosec // the int32 is confirmed
return convertFromNumber[NumOut](int32(v.Int()))
case reflect.Uint32:
//nolint:gosec // the uint32 is confirmed
return convertFromNumber[NumOut](uint32(v.Uint()))
case reflect.Int64:
return convertFromNumber[NumOut](int64(v.Int()))
case reflect.Uint64:
return convertFromNumber[NumOut](uint64(v.Uint()))
case reflect.Float32:
return convertFromNumber[NumOut](float32(v.Float()))
case reflect.Float64:
return convertFromNumber[NumOut](float64(v.Float()))
case reflect.Bool:
o := 0
if v.Bool() {
o = 1
}
return NumOut(o), nil
case reflect.String:
return convertFromString[NumOut](v.String())
}
return 0, errorHelper{
err: fmt.Errorf("%w from %T", ErrUnsupportedConversion, orig),
}
}
// MustConvert calls [Convert] to convert the value to the desired type, and panics if the conversion fails.
func MustConvert[NumOut Number, NumIn Input](orig NumIn) NumOut {
converted, err := Convert[NumOut](orig)
if err != nil {
panic(err)
}
return converted
}
func convertFromNumber[NumOut Number, NumIn Number](orig NumIn) (converted NumOut, err error) {
converted = NumOut(orig)
// floats could be compared directly
switch any(converted).(type) {
case float64:
// float64 cannot overflow, so we don't have to worry about it
return converted, nil
case float32:
origFloat64, isFloat64 := any(orig).(float64)
if !isFloat64 {
// only float64 can overflow float32
// everything else can be safely converted
return converted, nil
}
// check boundary
if math.Abs(origFloat64) < math.MaxFloat32 {
// the value is within float32 range, there is no overflow
return converted, nil
}
// TODO: check for numbers close to math.MaxFloat32
boundary := getUpperBoundary(converted)
errBoundary := ErrExceedMaximumValue
if negative(orig) {
boundary = getLowerBoundary(converted)
errBoundary = ErrExceedMinimumValue
}
return 0, errorHelper{
value: orig,
err: errBoundary,
boundary: boundary,
}
}
errBoundary := ErrExceedMaximumValue
boundary := getUpperBoundary(converted)
if negative(orig) {
errBoundary = ErrExceedMinimumValue
boundary = getLowerBoundary(converted)
}
if !sameSign(orig, converted) {
return 0, errorHelper{
value: orig,
err: errBoundary,
boundary: boundary,
}
}
// convert back to the original type
cast := NumIn(converted)
// and compare
base := orig
switch f := any(orig).(type) {
case float64:
base = NumIn(math.Trunc(f))
case float32:
base = NumIn(math.Trunc(float64(f)))
}
// exact match
if cast == base {
return converted, nil
}
return 0, errorHelper{
value: orig,
err: errBoundary,
boundary: boundary,
}
}
func convertFromString[NumOut Number](s string) (converted NumOut, err error) {
s = strings.TrimSpace(s)
if b, err := strconv.ParseBool(s); err == nil {
if b {
return NumOut(1), nil
}
return NumOut(0), nil
}
if strings.Contains(s, ".") {
o, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0, errorHelper{
value: s,
err: fmt.Errorf("%w %v to %T", ErrStringConversion, s, converted),
}
}
return convertFromNumber[NumOut](o)
}
if strings.HasPrefix(s, "-") {
o, err := strconv.ParseInt(s, 0, 64)
if err != nil {
if errors.Is(err, strconv.ErrRange) {
return 0, errorHelper{
value: s,
err: ErrExceedMinimumValue,
boundary: math.MinInt,
}
}
return 0, errorHelper{
value: s,
err: fmt.Errorf("%w %v to %T", ErrStringConversion, s, converted),
}
}
return convertFromNumber[NumOut](o)
}
o, err := strconv.ParseUint(s, 0, 64)
if err != nil {
if errors.Is(err, strconv.ErrRange) {
return 0, errorHelper{
value: s,
err: ErrExceedMaximumValue,
boundary: uint(math.MaxUint),
}
}
return 0, errorHelper{
value: s,
err: fmt.Errorf("%w %v to %T", ErrStringConversion, s, converted),
}
}
return convertFromNumber[NumOut](o)
}
// ToInt attempts to convert any [Type] value to an int.
// If the conversion results in a value outside the range of an int,
// an [ErrConversionIssue] error is returned.
func ToInt[T Number](i T) (int, error) {
return convertFromNumber[int](i)
}
// ToUint attempts to convert any [Number] value to an uint.
// If the conversion results in a value outside the range of an uint,
// an [ErrConversionIssue] error is returned.
func ToUint[T Number](i T) (uint, error) {
return convertFromNumber[uint](i)
}
// ToInt8 attempts to convert any [Number] value to an int8.
// If the conversion results in a value outside the range of an int8,
// an [ErrConversionIssue] error is returned.
func ToInt8[T Number](i T) (int8, error) {
return convertFromNumber[int8](i)
}
// ToUint8 attempts to convert any [Number] value to an uint8.
// If the conversion results in a value outside the range of an uint8,
// an [ErrConversionIssue] error is returned.
func ToUint8[T Number](i T) (uint8, error) {
return convertFromNumber[uint8](i)
}
// ToInt16 attempts to convert any [Number] value to an int16.
// If the conversion results in a value outside the range of an int16,
// an [ErrConversionIssue] error is returned.
func ToInt16[T Number](i T) (int16, error) {
return convertFromNumber[int16](i)
}
// ToUint16 attempts to convert any [Number] value to an uint16.
// If the conversion results in a value outside the range of an uint16,
// an [ErrConversionIssue] error is returned.
func ToUint16[T Number](i T) (uint16, error) {
return convertFromNumber[uint16](i)
}
// ToInt32 attempts to convert any [Number] value to an int32.
// If the conversion results in a value outside the range of an int32,
// an [ErrConversionIssue] error is returned.
func ToInt32[T Number](i T) (int32, error) {
return convertFromNumber[int32](i)
}
// ToUint32 attempts to convert any [Number] value to an uint32.
// If the conversion results in a value outside the range of an uint32,
// an [ErrConversionIssue] error is returned.
func ToUint32[T Number](i T) (uint32, error) {
return convertFromNumber[uint32](i)
}
// ToInt64 attempts to convert any [Number] value to an int64.
// If the conversion results in a value outside the range of an int64,
// an [ErrConversionIssue] error is returned.
func ToInt64[T Number](i T) (int64, error) {
return convertFromNumber[int64](i)
}
// ToUint64 attempts to convert any [Number] value to an uint64.
// If the conversion results in a value outside the range of an uint64,
// an [ErrConversionIssue] error is returned.
func ToUint64[T Number](i T) (uint64, error) {
return convertFromNumber[uint64](i)
}
// ToFloat32 attempts to convert any [Number] value to a float32.
// If the conversion results in a value outside the range of a float32,
// an [ErrConversionIssue] error is returned.
func ToFloat32[T Number](i T) (float32, error) {
return convertFromNumber[float32](i)
}
// ToFloat64 attempts to convert any [Number] value to a float64.
// If the conversion results in a value outside the range of a float64,
// an [ErrConversionIssue] error is returned.
func ToFloat64[T Number](i T) (float64, error) {
return convertFromNumber[float64](i)
}
go-safecast-1.6.0/conversion_64bit_test.go 0000664 0000000 0000000 00000004163 14764332721 0020512 0 ustar 00root root 0000000 0000000 //go:build !386 && !arm
package safecast_test
// The tests in conversion_test.go are the ones that are not architecture dependent
// The tests in conversion_64bit_test.go complete them for 64-bit systems
//
// This architecture dependent file covers the fact, you can reach a higher value with int and uint
// on 64-bit systems, but you will get a compile error on 32-bit.
// This is why it needs to be tested in an architecture dependent way.
import (
"math"
"testing"
"github.com/ccoveille/go-safecast"
)
func TestToInt32_64bit(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertInt32Error(t, []caseInt32[int]{
{name: "positive out of range", input: math.MaxInt32 + 1},
{name: "negative out of range", input: math.MinInt32 - 1},
})
})
}
func TestToUint32_64bit(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertUint32Error(t, []caseUint32[int]{
{name: "positive out of range", input: math.MaxUint32 + 1},
{name: "negative value", input: -1},
})
})
}
func TestToInt64_64bit(t *testing.T) {
t.Run("from uint", func(t *testing.T) {
assertInt64Error(t, []caseInt64[uint]{
{name: "positive out of range", input: math.MaxInt64 + 1},
})
})
}
func TestToInt_64bit(t *testing.T) {
t.Run("from uint", func(t *testing.T) {
assertIntError(t, []caseInt[uint]{
{name: "positive out of range", input: math.MaxInt64 + 1},
})
})
t.Run("from float64", func(t *testing.T) {
assertIntOK(t, []caseInt[float64]{
{name: "math.MinInt64", input: math.MinInt64, want: math.MinInt64}, // pass because of float imprecision
})
})
}
// TestConvert_64bit completes the [TestConvert] tests in conversion_test.go
// it contains the tests that can only works on 64-bit systems
func TestConvert_64bit(t *testing.T) {
t.Run("to uint32", func(t *testing.T) {
for name, tt := range map[string]struct {
input uint64
want uint32
}{
"positive out of range": {input: uint64(math.MaxUint32 + 1), want: 0},
} {
t.Run(name, func(t *testing.T) {
got, err := safecast.Convert[uint32](tt.input)
assertEqual(t, tt.want, got)
requireErrorIs(t, err, safecast.ErrConversionIssue)
})
}
})
}
go-safecast-1.6.0/conversion_test.go 0000664 0000000 0000000 00000216544 14764332721 0017512 0 ustar 00root root 0000000 0000000 package safecast_test
// The tests in conversion_test.go are the ones that are not architecture dependent
// The tests in conversion_64bit_test.go complete them for 64-bit systems
//
// The architecture dependent file covers the fact, you can reach a higher value with int and uint
// on 64-bit systems, but you will get a compile error on 32-bit.
// This is why it needs to be tested in an architecture dependent way.
import (
"errors"
"math"
"testing"
"github.com/ccoveille/go-safecast"
)
func TestToInt8(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertInt8OK(t, []caseInt8[int]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
{name: "negative within range", input: -100, want: -100},
})
assertInt8Error(t, []caseInt8[int]{
{name: "positive out of range", input: math.MaxInt8 + 1},
{name: "negative out of range", input: math.MinInt8 - 1},
})
})
t.Run("from int8", func(t *testing.T) {
assertInt8OK(t, []caseInt8[int8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
{name: "negative within range", input: -100, want: -100},
})
})
t.Run("from int16", func(t *testing.T) {
assertInt8OK(t, []caseInt8[int16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
{name: "negative within range", input: -100, want: -100},
})
assertInt8Error(t, []caseInt8[int16]{
{name: "positive out of range", input: math.MaxInt8 + 1},
{name: "negative out of range", input: math.MinInt8 - 1},
})
})
t.Run("from int32", func(t *testing.T) {
assertInt8OK(t, []caseInt8[int32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
{name: "negative within range", input: -100, want: -100},
})
assertInt8Error(t, []caseInt8[int32]{
{name: "positive out of range", input: math.MaxInt8 + 1},
{name: "negative out of range", input: math.MinInt8 - 1},
})
})
t.Run("from int64", func(t *testing.T) {
assertInt8OK(t, []caseInt8[int64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
{name: "negative within range", input: -100, want: -100},
})
assertInt8Error(t, []caseInt8[int64]{
{name: "positive out of range", input: math.MaxInt8 + 1},
{name: "negative out of range", input: math.MinInt8 - 1},
})
})
t.Run("from uint", func(t *testing.T) {
assertInt8OK(t, []caseInt8[uint]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt8Error(t, []caseInt8[uint]{
{name: "positive out of range", input: math.MaxInt8 + 1},
})
})
t.Run("from uint8", func(t *testing.T) {
assertInt8OK(t, []caseInt8[uint8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint16", func(t *testing.T) {
assertInt8OK(t, []caseInt8[uint16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt8Error(t, []caseInt8[uint16]{
{name: "positive out of range", input: math.MaxInt8 + 1},
})
})
t.Run("from uint32", func(t *testing.T) {
assertInt8OK(t, []caseInt8[uint32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt8Error(t, []caseInt8[uint32]{
{name: "positive out of range", input: math.MaxInt8 + 1},
})
})
t.Run("from uint64", func(t *testing.T) {
assertInt8OK(t, []caseInt8[uint64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt8Error(t, []caseInt8[uint64]{
{name: "positive out of range", input: math.MaxInt8 + 1},
})
})
t.Run("from float32", func(t *testing.T) {
assertInt8OK(t, []caseInt8[float32]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
})
assertInt8Error(t, []caseInt8[float32]{
{name: "positive out of range", input: math.MaxInt8 + 1},
})
})
t.Run("from float64", func(t *testing.T) {
assertInt8OK(t, []caseInt8[float64]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
})
assertInt8Error(t, []caseInt8[float64]{
{name: "positive out of range", input: math.MaxInt8 + 1},
})
})
}
func TestToUint8(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertUint8OK(t, []caseUint8[int]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint8Error(t, []caseUint8[int]{
{name: "positive out of range", input: math.MaxUint8 + 1},
{name: "negative value", input: -1},
})
})
t.Run("from int8", func(t *testing.T) {
assertUint8OK(t, []caseUint8[int8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint8Error(t, []caseUint8[int8]{
{name: "negative value", input: -1},
})
})
t.Run("from int16", func(t *testing.T) {
assertUint8OK(t, []caseUint8[int16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint8Error(t, []caseUint8[int16]{
{name: "positive out of range", input: 10000},
{name: "negative value", input: -1},
})
})
t.Run("from int32", func(t *testing.T) {
assertUint8OK(t, []caseUint8[int32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint8Error(t, []caseUint8[int32]{
{name: "positive out of range", input: 100000},
{name: "negative value", input: -1},
})
})
t.Run("from int64", func(t *testing.T) {
assertUint8OK(t, []caseUint8[int64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint8Error(t, []caseUint8[int64]{
{name: "positive out of range", input: 100000},
{name: "negative value", input: -1},
})
})
t.Run("from uint", func(t *testing.T) {
assertUint8OK(t, []caseUint8[uint]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint8Error(t, []caseUint8[uint]{
{name: "positive out of range", input: math.MaxUint8 + 1},
})
})
t.Run("from uint8", func(t *testing.T) {
assertUint8OK(t, []caseUint8[uint8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint16", func(t *testing.T) {
assertUint8OK(t, []caseUint8[uint16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint8Error(t, []caseUint8[uint]{
{name: "positive out of range", input: math.MaxUint8 + 1},
})
})
t.Run("from uint32", func(t *testing.T) {
assertUint8OK(t, []caseUint8[uint32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint8Error(t, []caseUint8[uint]{
{name: "positive out of range", input: math.MaxUint8 + 1},
})
})
t.Run("from uint64", func(t *testing.T) {
assertUint8OK(t, []caseUint8[uint64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint8Error(t, []caseUint8[uint]{
{name: "positive out of range", input: math.MaxUint8 + 1},
})
})
t.Run("from float32", func(t *testing.T) {
assertUint8OK(t, []caseUint8[float32]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 100.9, want: 100},
})
assertUint8Error(t, []caseUint8[float32]{
{name: "positive out of range", input: math.MaxUint8 + 1},
})
})
t.Run("from float64", func(t *testing.T) {
assertUint8OK(t, []caseUint8[float64]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 100.9, want: 100},
})
assertUint8Error(t, []caseUint8[float64]{
{name: "positive out of range", input: math.MaxUint8 + 1},
})
})
}
func TestToInt16(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertInt16OK(t, []caseInt16[int]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
assertInt16Error(t, []caseInt16[int]{
{name: "positive out of range", input: math.MaxInt16 + 1},
{name: "negative out of range", input: math.MinInt16 - 1},
})
})
t.Run("from int8", func(t *testing.T) {
assertInt16OK(t, []caseInt16[int8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
{name: "negative within range", input: -100, want: -100},
})
})
t.Run("from int16", func(t *testing.T) {
assertInt16OK(t, []caseInt16[int16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
})
t.Run("from int32", func(t *testing.T) {
assertInt16OK(t, []caseInt16[int32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
assertInt16Error(t, []caseInt16[int32]{
{name: "positive out of range", input: 100000},
{name: "negative out of range", input: -100000},
})
})
t.Run("from int64", func(t *testing.T) {
assertInt16OK(t, []caseInt16[int64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
assertInt16Error(t, []caseInt16[int64]{
{name: "positive out of range", input: 100000},
{name: "negative out of range", input: -100000},
})
})
t.Run("from uint", func(t *testing.T) {
assertInt16OK(t, []caseInt16[uint]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt16Error(t, []caseInt16[uint]{
{name: "positive out of range", input: math.MaxInt16 + 1},
})
})
t.Run("from uint8", func(t *testing.T) {
assertInt16OK(t, []caseInt16[uint8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint16", func(t *testing.T) {
assertInt16OK(t, []caseInt16[uint16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt16Error(t, []caseInt16[uint16]{
{name: "positive out of range", input: math.MaxInt16 + 1},
})
})
t.Run("from uint32", func(t *testing.T) {
assertInt16OK(t, []caseInt16[uint32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt16Error(t, []caseInt16[uint32]{
{name: "positive out of range", input: math.MaxInt16 + 1},
})
})
t.Run("from uint64", func(t *testing.T) {
assertInt16OK(t, []caseInt16[uint64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt16Error(t, []caseInt16[uint64]{
{name: "positive out of range", input: math.MaxInt16 + 1},
})
})
t.Run("from float32", func(t *testing.T) {
assertInt16OK(t, []caseInt16[float32]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000, want: 10000},
})
assertInt16Error(t, []caseInt16[float32]{
{name: "positive out of range", input: math.MaxInt16 + 1},
})
})
t.Run("from float64", func(t *testing.T) {
assertInt16OK(t, []caseInt16[float64]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000, want: 10000},
})
assertInt16Error(t, []caseInt16[float64]{
{name: "positive out of range", input: math.MaxInt16 + 1},
})
})
}
func TestToUint16(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertUint16OK(t, []caseUint16[int]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint16Error(t, []caseUint16[int]{
{name: "positive out of range", input: math.MaxUint16 + 1},
{name: "negative value", input: -1},
})
})
t.Run("from int8", func(t *testing.T) {
assertUint16OK(t, []caseUint16[int8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from int16", func(t *testing.T) {
assertUint16OK(t, []caseUint16[int16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
})
t.Run("from int32", func(t *testing.T) {
assertUint16OK(t, []caseUint16[int32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint16Error(t, []caseUint16[int32]{
{name: "positive out of range", input: 100000},
{name: "negative value", input: -1},
})
})
t.Run("from int64", func(t *testing.T) {
assertUint16OK(t, []caseUint16[int64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint16Error(t, []caseUint16[int64]{
{name: "positive out of range", input: 100000},
{name: "negative value", input: -1},
})
})
t.Run("from uint", func(t *testing.T) {
assertUint16OK(t, []caseUint16[uint]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint16Error(t, []caseUint16[uint]{
{name: "positive out of range", input: math.MaxUint16 + 1},
})
})
t.Run("from uint8", func(t *testing.T) {
assertUint16OK(t, []caseUint16[uint8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint16", func(t *testing.T) {
assertUint16OK(t, []caseUint16[uint16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint32", func(t *testing.T) {
assertUint16OK(t, []caseUint16[uint32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint16Error(t, []caseUint16[uint32]{
{name: "positive out of range", input: math.MaxUint16 + 1},
})
})
t.Run("from uint64", func(t *testing.T) {
assertUint16OK(t, []caseUint16[uint64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint16Error(t, []caseUint16[uint64]{
{name: "positive out of range", input: math.MaxUint16 + 1},
})
})
t.Run("from float32", func(t *testing.T) {
assertUint16OK(t, []caseUint16[float32]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint16Error(t, []caseUint16[float32]{
{name: "positive out of range", input: math.MaxUint16 + 1},
})
})
t.Run("from float64", func(t *testing.T) {
assertUint16OK(t, []caseUint16[float64]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint16Error(t, []caseUint16[float32]{
{name: "positive out of range", input: math.MaxUint16 + 1},
})
})
}
func TestToInt32(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertInt32OK(t, []caseInt32[int]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
// There are extra checks in [TestToInt32_64bit]
// the tests are separated because they cannot work on i386
})
t.Run("from int8", func(t *testing.T) {
assertInt32OK(t, []caseInt32[int8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
{name: "negative within range", input: -100, want: -100},
})
})
t.Run("from int16", func(t *testing.T) {
assertInt32OK(t, []caseInt32[int16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
})
t.Run("from int32", func(t *testing.T) {
assertInt32OK(t, []caseInt32[int32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
})
t.Run("from int64", func(t *testing.T) {
assertInt32OK(t, []caseInt32[int64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
assertInt32Error(t, []caseInt32[int64]{
{name: "positive out of range", input: math.MaxInt32 + 1},
{name: "negative out of range", input: math.MinInt32 - 1},
})
})
t.Run("from uint", func(t *testing.T) {
assertInt32OK(t, []caseInt32[uint]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt32Error(t, []caseInt32[uint]{
{name: "positive out of range", input: math.MaxInt32 + 1},
})
})
t.Run("from uint8", func(t *testing.T) {
assertInt32OK(t, []caseInt32[uint8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint16", func(t *testing.T) {
assertInt32OK(t, []caseInt32[uint16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: math.MaxUint16, want: math.MaxUint16},
})
})
t.Run("from uint32", func(t *testing.T) {
assertInt32OK(t, []caseInt32[uint32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt32Error(t, []caseInt32[uint32]{
{name: "positive out of range", input: math.MaxInt32 + 1},
})
})
t.Run("from uint64", func(t *testing.T) {
assertInt32OK(t, []caseInt32[uint64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt32Error(t, []caseInt32[uint64]{
{name: "positive out of range", input: math.MaxInt32 + 1},
})
})
t.Run("from float32", func(t *testing.T) {
assertInt32OK(t, []caseInt32[float32]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000, want: 10000},
})
assertInt32Error(t, []caseInt32[float32]{
{name: "positive out of range", input: math.MaxInt32 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
})
})
t.Run("from float64", func(t *testing.T) {
assertInt32OK(t, []caseInt32[float64]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000, want: 10000},
})
assertInt32Error(t, []caseInt32[float64]{
{name: "positive out of range", input: math.MaxInt32 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
})
})
}
func TestToUint32(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertUint32OK(t, []caseUint32[int]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint32Error(t, []caseUint32[int]{
// There are extra checks in [TestToUint32_64bit]
// the tests are separated because they cannot work on i386
{name: "negative value", input: -1},
})
})
t.Run("from int8", func(t *testing.T) {
assertUint32OK(t, []caseUint32[int8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint32Error(t, []caseUint32[int8]{
{name: "negative value", input: -1},
})
})
t.Run("from int16", func(t *testing.T) {
assertUint32OK(t, []caseUint32[int16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint32Error(t, []caseUint32[int16]{
{name: "negative value", input: -1},
})
})
t.Run("from int32", func(t *testing.T) {
assertUint32OK(t, []caseUint32[int32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint32Error(t, []caseUint32[int32]{
{name: "negative value", input: -1},
})
})
t.Run("from int64", func(t *testing.T) {
assertUint32OK(t, []caseUint32[int64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint32Error(t, []caseUint32[int64]{
{name: "positive out of range", input: math.MaxUint32 + 1},
{name: "negative value", input: -1},
})
})
t.Run("from uint", func(t *testing.T) {
assertUint32OK(t, []caseUint32[uint]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
// There are extra checks in [TestToUint32_64bit]
// the tests are separated because they cannot work on i386
})
t.Run("from uint8", func(t *testing.T) {
assertUint32OK(t, []caseUint32[uint8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint16", func(t *testing.T) {
assertUint32OK(t, []caseUint32[uint16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint32", func(t *testing.T) {
assertUint32OK(t, []caseUint32[uint32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint64", func(t *testing.T) {
assertUint32OK(t, []caseUint32[uint64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint32Error(t, []caseUint32[uint64]{
{name: "positive out of range", input: math.MaxUint32 + 1},
})
})
t.Run("from float32", func(t *testing.T) {
assertUint32OK(t, []caseUint32[float32]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000.9, want: 10000},
})
assertUint32Error(t, []caseUint32[float32]{
{name: "positive out of range", input: math.MaxUint32 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
})
})
t.Run("from float64", func(t *testing.T) {
assertUint32OK(t, []caseUint32[float64]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000.9, want: 10000},
})
assertUint32Error(t, []caseUint32[float64]{
{name: "positive out of range", input: math.MaxUint32 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "negative value", input: -1},
})
})
}
func TestToInt64(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertInt64OK(t, []caseInt64[int]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
})
t.Run("from int8", func(t *testing.T) {
assertInt64OK(t, []caseInt64[int8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
{name: "negative within range", input: -100, want: -100},
})
})
t.Run("from int16", func(t *testing.T) {
assertInt64OK(t, []caseInt64[int16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
})
t.Run("from int32", func(t *testing.T) {
assertInt64OK(t, []caseInt64[int32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
})
t.Run("from int64", func(t *testing.T) {
assertInt64OK(t, []caseInt64[int64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
})
t.Run("from uint", func(t *testing.T) {
assertInt64OK(t, []caseInt64[uint]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
// There are extra checks in [TestToInt64_64bit]
// the tests are separated because they cannot work on i386
})
t.Run("from uint8", func(t *testing.T) {
assertInt64OK(t, []caseInt64[uint8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint16", func(t *testing.T) {
assertInt64OK(t, []caseInt64[uint16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint32", func(t *testing.T) {
assertInt64OK(t, []caseInt64[uint32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint64", func(t *testing.T) {
assertInt64OK(t, []caseInt64[uint64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertInt64Error(t, []caseInt64[uint64]{
{name: "positive out of range", input: math.MaxInt64 + 1},
})
})
t.Run("from float32", func(t *testing.T) {
assertInt64OK(t, []caseInt64[float32]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000.9, want: 10000},
{name: "max int16", input: math.MaxInt16, want: math.MaxInt16},
{name: "min int16", input: math.MinInt16, want: math.MinInt16},
{name: "max int32", input: math.MaxInt32, want: 2147483648}, // number differs due to float imprecision
{name: "min int32", input: math.MinInt32, want: math.MinInt32},
})
assertInt64Error(t, []caseInt64[float32]{
{name: "out of range math.MaxInt64", input: math.MaxInt64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range math.MaxUint64", input: math.MaxUint64},
{name: "out of range math.MinInt64", input: math.MinInt64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range math.MaxFloat32", input: math.MaxFloat32},
{name: "out of range -math.MaxFloat32", input: -math.MaxFloat32},
})
})
t.Run("from float64", func(t *testing.T) {
assertInt64OK(t, []caseInt64[float64]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000.9, want: 10000},
{name: "max int16", input: math.MaxInt16, want: math.MaxInt16},
{name: "min int16", input: math.MinInt16, want: math.MinInt16},
{name: "max int32", input: math.MaxInt32, want: math.MaxInt32},
{name: "min int32", input: math.MinInt32, want: math.MinInt32},
})
assertInt64Error(t, []caseInt64[float64]{
{name: "out of range math.MaxInt64 + 1", input: math.MaxInt64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range math.MaxUint64", input: math.MaxUint64},
{name: "out of range math.MinInt64", input: math.MinInt64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range math.MaxFloat32", input: math.MaxFloat32},
{name: "out of range -math.MaxFloat32", input: -math.MaxFloat32},
{name: "out of range math.MaxFloat64", input: math.MaxFloat64},
{name: "out of range -math.MaxFloat64", input: -math.MaxFloat64},
})
})
}
func TestToUint64(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertUint64OK(t, []caseUint64[int]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint64Error(t, []caseUint64[int]{
{name: "negative value", input: -1},
})
})
t.Run("from int8", func(t *testing.T) {
assertUint64OK(t, []caseUint64[int8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUint64Error(t, []caseUint64[int]{
{name: "negative value", input: -1},
})
})
t.Run("from int16", func(t *testing.T) {
assertUint64OK(t, []caseUint64[int16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint64Error(t, []caseUint64[int]{
{name: "negative value", input: -1},
})
})
t.Run("from int32", func(t *testing.T) {
assertUint64OK(t, []caseUint64[int32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint64Error(t, []caseUint64[int]{
{name: "negative value", input: -1},
})
})
t.Run("from int64", func(t *testing.T) {
assertUint64OK(t, []caseUint64[int64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUint64Error(t, []caseUint64[int]{
{name: "negative value", input: -1},
})
})
t.Run("from uint", func(t *testing.T) {
assertUint64OK(t, []caseUint64[uint]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint8", func(t *testing.T) {
assertUint64OK(t, []caseUint64[uint8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint16", func(t *testing.T) {
assertUint64OK(t, []caseUint64[uint16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint32", func(t *testing.T) {
assertUint64OK(t, []caseUint64[uint32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint64", func(t *testing.T) {
assertUint64OK(t, []caseUint64[uint64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from float32", func(t *testing.T) {
assertUint64OK(t, []caseUint64[float32]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000.9, want: 10000},
})
assertUint64Error(t, []caseUint64[float32]{
{name: "negative value", input: -1},
{name: "out of range max uint64", input: math.MaxUint64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
})
})
t.Run("from float64", func(t *testing.T) {
assertUint64OK(t, []caseUint64[float64]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000.9, want: 10000},
})
assertUint64Error(t, []caseUint64[float64]{
{name: "negative value", input: -1},
{name: "out of range max uint64", input: math.MaxUint64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range max float32", input: math.MaxFloat32},
{name: "out of range max float64", input: math.MaxFloat64},
})
})
}
func TestToInt(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertIntOK(t, []caseInt[int]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
})
t.Run("from int8", func(t *testing.T) {
assertIntOK(t, []caseInt[int8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
{name: "negative within range", input: -100, want: -100},
})
})
t.Run("from int16", func(t *testing.T) {
assertIntOK(t, []caseInt[int16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
})
t.Run("from int32", func(t *testing.T) {
assertIntOK(t, []caseInt[int32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
})
t.Run("from int64", func(t *testing.T) {
assertIntOK(t, []caseInt[int64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
{name: "negative within range", input: -10000, want: -10000},
})
})
t.Run("from uint", func(t *testing.T) {
assertIntOK(t, []caseInt[uint]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
// There are extra checks in [TestToInt_64bit]
// the tests are separated because they cannot work on i386
})
t.Run("from uint8", func(t *testing.T) {
assertIntOK(t, []caseInt[uint8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint16", func(t *testing.T) {
assertIntOK(t, []caseInt[uint16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint32", func(t *testing.T) {
assertIntOK(t, []caseInt[uint32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint64", func(t *testing.T) {
assertIntOK(t, []caseInt[uint64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertIntError(t, []caseInt[uint64]{
{name: "positive out of range", input: math.MaxInt64 + 1},
})
})
t.Run("from float32", func(t *testing.T) {
assertIntOK(t, []caseInt[float32]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000.9, want: 10000},
})
assertIntError(t, []caseInt[float32]{
{name: "out of range math.MaxInt64 + 1", input: math.MaxInt64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range math.MaxUint64", input: math.MaxUint64},
{name: "out of range math.MinInt64", input: math.MinInt64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range math.MaxFloat32", input: math.MaxFloat32},
{name: "out of range -math.MaxFloat32", input: -math.MaxFloat32},
})
})
t.Run("from float64", func(t *testing.T) {
assertIntOK(t, []caseInt[float64]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000.9, want: 10000},
// There are extra checks in [TestToInt_64bit]
// the tests are separated because they cannot work on i386
})
assertIntError(t, []caseInt[float64]{
{name: "out of range math.MaxInt64 + 1", input: math.MaxInt64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range math.MaxUint64", input: math.MaxUint64},
{name: "out of range math.MinInt64", input: math.MinInt64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range math.MaxFloat32", input: math.MaxFloat32},
{name: "out of range -math.MaxFloat32", input: -math.MaxFloat32},
{name: "out of range math.MaxFloat64", input: math.MaxFloat64},
{name: "out of range -math.MaxFloat64", input: -math.MaxFloat64},
})
})
}
func TestToUint(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertUintOK(t, []caseUint[int]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUintError(t, []caseUint[int]{
{name: "negative value", input: -1},
})
})
t.Run("from int8", func(t *testing.T) {
assertUintOK(t, []caseUint[int8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
assertUintError(t, []caseUint[int8]{
{name: "negative value", input: -1},
})
})
t.Run("from int16", func(t *testing.T) {
assertUintOK(t, []caseUint[int16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUintError(t, []caseUint[int16]{
{name: "negative value", input: -1},
})
})
t.Run("from int32", func(t *testing.T) {
assertUintOK(t, []caseUint[int32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUintError(t, []caseUint[int32]{
{name: "negative value", input: -1},
})
})
t.Run("from int64", func(t *testing.T) {
assertUintOK(t, []caseUint[int64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 10000, want: 10000},
})
assertUintError(t, []caseUint[int64]{
{name: "negative value", input: -1},
})
})
t.Run("from uint", func(t *testing.T) {
assertUintOK(t, []caseUint[uint]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint8", func(t *testing.T) {
assertUintOK(t, []caseUint[uint8]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint16", func(t *testing.T) {
assertUintOK(t, []caseUint[uint16]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint32", func(t *testing.T) {
assertUintOK(t, []caseUint[uint32]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from uint64", func(t *testing.T) {
assertUintOK(t, []caseUint[uint64]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
t.Run("from float32", func(t *testing.T) {
assertUintOK(t, []caseUint[float32]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000.9, want: 10000},
})
assertUint64Error(t, []caseUint64[float32]{
{name: "negative value", input: -1},
{name: "out of range max uint64", input: math.MaxUint64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range max float32", input: math.MaxFloat32},
})
})
t.Run("from float64", func(t *testing.T) {
assertUintOK(t, []caseUint[float64]{
{name: "zero", input: 0.0, want: 0},
{name: "rounded value", input: 1.1, want: 1},
{name: "positive within range", input: 10000.9, want: 10000},
})
assertUintError(t, []caseUint[float64]{
{name: "negative value", input: -1},
{name: "out of range max uint64", input: math.MaxUint64 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range max float32", input: math.MaxFloat32},
{name: "out of range max float64", input: math.MaxFloat64},
})
})
type UintAlias uint
t.Run("from type alias", func(t *testing.T) {
assertUintOK(t, []caseUint[UintAlias]{
{name: "zero", input: 0, want: 0},
{name: "positive within range", input: 100, want: 100},
})
})
}
func TestToFloat32(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[int]{
{name: "zero", input: 0, want: 0.0},
{name: "negative within range", input: -100, want: -100.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from int8", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[int8]{
{name: "zero", input: 0, want: 0.0},
{name: "negative within range", input: -100, want: -100.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from int16", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[int16]{
{name: "zero", input: 0, want: 0.0},
{name: "negative within range", input: -100, want: -100.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from int32", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[int32]{
{name: "zero", input: 0, want: 0.0},
{name: "negative within range", input: -100, want: -100.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from int64", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[int64]{
{name: "zero", input: 0, want: 0.0},
{name: "negative within range", input: -100, want: -100.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from uint", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[uint]{
{name: "zero", input: 0, want: 0.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from uint8", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[uint8]{
{name: "zero", input: 0, want: 0.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from uint16", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[uint16]{
{name: "zero", input: 0, want: 0.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from uint32", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[uint32]{
{name: "zero", input: 0, want: 0.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from uint64", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[uint64]{
{name: "zero", input: 0, want: 0.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from float32", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[float32]{
{name: "zero", input: 0.0, want: 0.0},
{name: "rounded value", input: 1.1, want: 1.1},
{name: "negative within range", input: -100.9, want: -100.9},
{name: "positive within range", input: 100.9, want: 100.9},
})
})
t.Run("from float64", func(t *testing.T) {
assertFloat32OK(t, []caseFloat32[float64]{
{name: "zero", input: 0.0, want: 0.0},
{name: "negative zero", input: math.Copysign(0, -1), want: -0},
{name: "almost zero", input: math.SmallestNonzeroFloat32, want: 1e-45},
{name: "almost negative zero", input: -math.SmallestNonzeroFloat32, want: -1e-45},
{name: "negative within range", input: -100.9, want: -100.9},
{name: "positive within range", input: 100.9, want: 100.9},
{name: "with imprecision due to conversion", input: 2.67428e+28, want: 2.67428e+28},
})
assertFloat32Error(t, []caseFloat32[float64]{
{name: "out of range max float32", input: math.MaxFloat32 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
{name: "out of range min float32", input: -math.MaxFloat32 * 1.02}, // because of float imprecision, we have to exceed the min int64 to trigger the error
})
})
}
func TestToFloat64(t *testing.T) {
t.Run("from int", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[int]{
{name: "zero", input: 0, want: 0.0},
{name: "negative within range", input: -100, want: -100.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from int8", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[int8]{
{name: "zero", input: 0, want: 0.0},
{name: "negative within range", input: -100, want: -100.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from int16", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[int16]{
{name: "zero", input: 0, want: 0.0},
{name: "negative within range", input: -100, want: -100.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from int32", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[int32]{
{name: "zero", input: 0, want: 0.0},
{name: "negative within range", input: -100, want: -100.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from int64", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[int64]{
{name: "zero", input: 0, want: 0.0},
{name: "negative within range", input: -100, want: -100.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from uint", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[uint]{
{name: "zero", input: 0, want: 0.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from uint8", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[uint8]{
{name: "zero", input: 0, want: 0.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from uint16", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[uint16]{
{name: "zero", input: 0, want: 0.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from uint32", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[uint32]{
{name: "zero", input: 0, want: 0.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from uint64", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[uint64]{
{name: "zero", input: 0, want: 0.0},
{name: "positive within range", input: 100, want: 100.0},
})
})
t.Run("from float32", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[float64]{
{name: "zero", input: 0.0, want: 0.0},
{name: "rounded value", input: 1.1, want: 1.1},
{name: "negative within range", input: -100.9, want: -100.9},
{name: "positive within range", input: 100.9, want: 100.9},
})
})
t.Run("from float64", func(t *testing.T) {
assertFloat64OK(t, []caseFloat64[float64]{
{name: "zero", input: 0.0, want: 0.0},
{name: "negative within range", input: -100.9, want: -100.9},
{name: "positive within range", input: 100.0, want: 100.0},
})
})
}
func Map[T any, U any](fn func(v T) U, input []T) []U {
var output []U
for _, v := range input {
output = append(output, fn(v))
}
return output
}
type MapTest[TypeInput safecast.Input, TypeOutput safecast.Number] struct {
Input TypeInput
ExpectedOutput TypeOutput
ExpectedError error
ErrorContains string
}
func (mt MapTest[I, O]) TestConvert(t *testing.T) {
// configure a helper to validate there is no panic
defer func(t *testing.T) {
t.Helper()
err := recover()
if err != nil {
t.Fatalf("panic with %v", err)
}
}(t)
out, err := safecast.Convert[O](mt.Input)
if mt.ExpectedError != nil {
requireErrorIs(t, err, safecast.ErrConversionIssue)
requireErrorIs(t, err, mt.ExpectedError)
if mt.ErrorContains != "" {
requireErrorContains(t, err, mt.ErrorContains)
}
return
}
assertNoError(t, err)
assertEqual(t, mt.ExpectedOutput, out)
}
type TestableConvert interface {
TestConvert(t *testing.T)
}
func TestConvert(t *testing.T) {
t.Run("untyped integer", func(t *testing.T) {
out, err := safecast.Convert[uint](42)
assertNoError(t, err)
assertEqual(t, uint(42), out)
})
for name, c := range map[string]TestableConvert{
"int to float32": MapTest[int, float32]{Input: 42, ExpectedOutput: 42},
"int to float64": MapTest[int, float64]{Input: 42, ExpectedOutput: 42},
"int to int": MapTest[int, int]{Input: 42, ExpectedOutput: 42},
"int to int16": MapTest[int, int16]{Input: 42, ExpectedOutput: 42},
"int to int32": MapTest[int, int32]{Input: 42, ExpectedOutput: 42},
"int to int64": MapTest[int, int64]{Input: 42, ExpectedOutput: 42},
"int to int8": MapTest[int, int8]{Input: 42, ExpectedOutput: 42},
"int to uint": MapTest[int, uint]{Input: 42, ExpectedOutput: 42},
"int to uint16": MapTest[int, uint16]{Input: 42, ExpectedOutput: 42},
"int to uint32": MapTest[int, uint32]{Input: 42, ExpectedOutput: 42},
"int to uint64": MapTest[int, uint64]{Input: 42, ExpectedOutput: 42},
"int to uint8": MapTest[int, uint8]{Input: 42, ExpectedOutput: 42},
"int8 to float32": MapTest[int8, float32]{Input: 42, ExpectedOutput: 42},
"int8 to float64": MapTest[int8, float64]{Input: 42, ExpectedOutput: 42},
"int8 to int": MapTest[int8, int]{Input: 42, ExpectedOutput: 42},
"int8 to int16": MapTest[int8, int16]{Input: 42, ExpectedOutput: 42},
"int8 to int32": MapTest[int8, int32]{Input: 42, ExpectedOutput: 42},
"int8 to int64": MapTest[int8, int64]{Input: 42, ExpectedOutput: 42},
"int8 to int8": MapTest[int8, int8]{Input: 42, ExpectedOutput: 42},
"int8 to uint": MapTest[int8, uint]{Input: 42, ExpectedOutput: 42},
"int8 to uint16": MapTest[int8, uint16]{Input: 42, ExpectedOutput: 42},
"int8 to uint32": MapTest[int8, uint32]{Input: 42, ExpectedOutput: 42},
"int8 to uint64": MapTest[int8, uint64]{Input: 42, ExpectedOutput: 42},
"int8 to uint8": MapTest[int8, uint8]{Input: 42, ExpectedOutput: 42},
"int16 to float32": MapTest[int16, float32]{Input: 42, ExpectedOutput: 42},
"int16 to float64": MapTest[int16, float64]{Input: 42, ExpectedOutput: 42},
"int16 to int": MapTest[int16, int]{Input: 42, ExpectedOutput: 42},
"int16 to int16": MapTest[int16, int16]{Input: 42, ExpectedOutput: 42},
"int16 to int32": MapTest[int16, int32]{Input: 42, ExpectedOutput: 42},
"int16 to int64": MapTest[int16, int64]{Input: 42, ExpectedOutput: 42},
"int16 to int8": MapTest[int16, int8]{Input: 42, ExpectedOutput: 42},
"int16 to uint": MapTest[int16, uint]{Input: 42, ExpectedOutput: 42},
"int16 to uint16": MapTest[int16, uint16]{Input: 42, ExpectedOutput: 42},
"int16 to uint32": MapTest[int16, uint32]{Input: 42, ExpectedOutput: 42},
"int16 to uint64": MapTest[int16, uint64]{Input: 42, ExpectedOutput: 42},
"int16 to uint8": MapTest[int16, uint8]{Input: 42, ExpectedOutput: 42},
"int32 to float32": MapTest[int32, float32]{Input: 42, ExpectedOutput: 42},
"int32 to float64": MapTest[int32, float64]{Input: 42, ExpectedOutput: 42},
"int32 to int": MapTest[int32, int]{Input: 42, ExpectedOutput: 42},
"int32 to int16": MapTest[int32, int16]{Input: 42, ExpectedOutput: 42},
"int32 to int32": MapTest[int32, int32]{Input: 42, ExpectedOutput: 42},
"int32 to int64": MapTest[int32, int64]{Input: 42, ExpectedOutput: 42},
"int32 to int8": MapTest[int32, int8]{Input: 42, ExpectedOutput: 42},
"int32 to uint": MapTest[int32, uint]{Input: 42, ExpectedOutput: 42},
"int32 to uint16": MapTest[int32, uint16]{Input: 42, ExpectedOutput: 42},
"int32 to uint32": MapTest[int32, uint32]{Input: 42, ExpectedOutput: 42},
"int32 to uint64": MapTest[int32, uint64]{Input: 42, ExpectedOutput: 42},
"int32 to uint8": MapTest[int32, uint8]{Input: 42, ExpectedOutput: 42},
"int64 to float32": MapTest[int64, float32]{Input: 42, ExpectedOutput: 42},
"int64 to float64": MapTest[int64, float64]{Input: 42, ExpectedOutput: 42},
"int64 to int": MapTest[int64, int]{Input: 42, ExpectedOutput: 42},
"int64 to int16": MapTest[int64, int16]{Input: 42, ExpectedOutput: 42},
"int64 to int32": MapTest[int64, int32]{Input: 42, ExpectedOutput: 42},
"int64 to int64": MapTest[int64, int64]{Input: 42, ExpectedOutput: 42},
"int64 to int8": MapTest[int64, int8]{Input: 42, ExpectedOutput: 42},
"int64 to uint": MapTest[int64, uint]{Input: 42, ExpectedOutput: 42},
"int64 to uint16": MapTest[int64, uint16]{Input: 42, ExpectedOutput: 42},
"int64 to uint32": MapTest[int64, uint32]{Input: 42, ExpectedOutput: 42},
"int64 to uint64": MapTest[int64, uint64]{Input: 42, ExpectedOutput: 42},
"int64 to uint8": MapTest[int64, uint8]{Input: 42, ExpectedOutput: 42},
"uint to float32": MapTest[uint, float32]{Input: 42, ExpectedOutput: 42},
"uint to float64": MapTest[uint, float64]{Input: 42, ExpectedOutput: 42},
"uint to int": MapTest[uint, int]{Input: 42, ExpectedOutput: 42},
"uint to int16": MapTest[uint, int16]{Input: 42, ExpectedOutput: 42},
"uint to int32": MapTest[uint, int32]{Input: 42, ExpectedOutput: 42},
"uint to int64": MapTest[uint, int64]{Input: 42, ExpectedOutput: 42},
"uint to int8": MapTest[uint, int8]{Input: 42, ExpectedOutput: 42},
"uint to uint": MapTest[uint, uint]{Input: 42, ExpectedOutput: 42},
"uint to uint16": MapTest[uint, uint16]{Input: 42, ExpectedOutput: 42},
"uint to uint32": MapTest[uint, uint32]{Input: 42, ExpectedOutput: 42},
"uint to uint64": MapTest[uint, uint64]{Input: 42, ExpectedOutput: 42},
"uint to uint8": MapTest[uint, uint8]{Input: 42, ExpectedOutput: 42},
"uint8 to float32": MapTest[uint8, float32]{Input: 42, ExpectedOutput: 42},
"uint8 to float64": MapTest[uint8, float64]{Input: 42, ExpectedOutput: 42},
"uint8 to int": MapTest[uint8, int]{Input: 42, ExpectedOutput: 42},
"uint8 to int16": MapTest[uint8, int16]{Input: 42, ExpectedOutput: 42},
"uint8 to int32": MapTest[uint8, int32]{Input: 42, ExpectedOutput: 42},
"uint8 to int64": MapTest[uint8, int64]{Input: 42, ExpectedOutput: 42},
"uint8 to int8": MapTest[uint8, int8]{Input: 42, ExpectedOutput: 42},
"uint8 to uint": MapTest[uint8, uint]{Input: 42, ExpectedOutput: 42},
"uint8 to uint16": MapTest[uint8, uint16]{Input: 42, ExpectedOutput: 42},
"uint8 to uint32": MapTest[uint8, uint32]{Input: 42, ExpectedOutput: 42},
"uint8 to uint64": MapTest[uint8, uint64]{Input: 42, ExpectedOutput: 42},
"uint8 to uint8": MapTest[uint8, uint8]{Input: 42, ExpectedOutput: 42},
"uint16 to float32": MapTest[uint16, float32]{Input: 42, ExpectedOutput: 42},
"uint16 to float64": MapTest[uint16, float64]{Input: 42, ExpectedOutput: 42},
"uint16 to int": MapTest[uint16, int]{Input: 42, ExpectedOutput: 42},
"uint16 to int16": MapTest[uint16, int16]{Input: 42, ExpectedOutput: 42},
"uint16 to int32": MapTest[uint16, int32]{Input: 42, ExpectedOutput: 42},
"uint16 to int64": MapTest[uint16, int64]{Input: 42, ExpectedOutput: 42},
"uint16 to int8": MapTest[uint16, int8]{Input: 42, ExpectedOutput: 42},
"uint16 to uint": MapTest[uint16, uint]{Input: 42, ExpectedOutput: 42},
"uint16 to uint16": MapTest[uint16, uint16]{Input: 42, ExpectedOutput: 42},
"uint16 to uint32": MapTest[uint16, uint32]{Input: 42, ExpectedOutput: 42},
"uint16 to uint64": MapTest[uint16, uint64]{Input: 42, ExpectedOutput: 42},
"uint16 to uint8": MapTest[uint16, uint8]{Input: 42, ExpectedOutput: 42},
"uint32 to float32": MapTest[uint32, float32]{Input: 42, ExpectedOutput: 42},
"uint32 to float64": MapTest[uint32, float64]{Input: 42, ExpectedOutput: 42},
"uint32 to int": MapTest[uint32, int]{Input: 42, ExpectedOutput: 42},
"uint32 to int16": MapTest[uint32, int16]{Input: 42, ExpectedOutput: 42},
"uint32 to int32": MapTest[uint32, int32]{Input: 42, ExpectedOutput: 42},
"uint32 to int64": MapTest[uint32, int64]{Input: 42, ExpectedOutput: 42},
"uint32 to int8": MapTest[uint32, int8]{Input: 42, ExpectedOutput: 42},
"uint32 to uint": MapTest[uint32, uint]{Input: 42, ExpectedOutput: 42},
"uint32 to uint16": MapTest[uint32, uint16]{Input: 42, ExpectedOutput: 42},
"uint32 to uint32": MapTest[uint32, uint32]{Input: 42, ExpectedOutput: 42},
"uint32 to uint64": MapTest[uint32, uint64]{Input: 42, ExpectedOutput: 42},
"uint32 to uint8": MapTest[uint32, uint8]{Input: 42, ExpectedOutput: 42},
"uint64 to float32": MapTest[uint64, float32]{Input: 42, ExpectedOutput: 42},
"uint64 to float64": MapTest[uint64, float64]{Input: 42, ExpectedOutput: 42},
"uint64 to int": MapTest[uint64, int]{Input: 42, ExpectedOutput: 42},
"uint64 to int16": MapTest[uint64, int16]{Input: 42, ExpectedOutput: 42},
"uint64 to int32": MapTest[uint64, int32]{Input: 42, ExpectedOutput: 42},
"uint64 to int64": MapTest[uint64, int64]{Input: 42, ExpectedOutput: 42},
"uint64 to int8": MapTest[uint64, int8]{Input: 42, ExpectedOutput: 42},
"uint64 to uint": MapTest[uint64, uint]{Input: 42, ExpectedOutput: 42},
"uint64 to uint16": MapTest[uint64, uint16]{Input: 42, ExpectedOutput: 42},
"uint64 to uint32": MapTest[uint64, uint32]{Input: 42, ExpectedOutput: 42},
"uint64 to uint64": MapTest[uint64, uint64]{Input: 42, ExpectedOutput: 42},
"uint64 to uint8": MapTest[uint64, uint8]{Input: 42, ExpectedOutput: 42},
"float32 to int": MapTest[float32, int]{Input: 42, ExpectedOutput: 42},
"float32 to int8": MapTest[float32, int8]{Input: 42, ExpectedOutput: 42},
"float32 to int16": MapTest[float32, int16]{Input: 42, ExpectedOutput: 42},
"float32 to int32": MapTest[float32, int32]{Input: 42, ExpectedOutput: 42},
"float32 to int64": MapTest[float32, int64]{Input: 42, ExpectedOutput: 42},
"float32 to uint": MapTest[float32, uint]{Input: 42, ExpectedOutput: 42},
"float32 to uint8": MapTest[float32, uint8]{Input: 42, ExpectedOutput: 42},
"float32 to uint16": MapTest[float32, uint16]{Input: 42, ExpectedOutput: 42},
"float32 to uint32": MapTest[float32, uint32]{Input: 42, ExpectedOutput: 42},
"float32 to uint64": MapTest[float32, uint64]{Input: 42, ExpectedOutput: 42},
"float32 to float32": MapTest[float32, float32]{Input: 42, ExpectedOutput: 42},
"float32 to float64": MapTest[float32, float64]{Input: 42, ExpectedOutput: 42},
"float64 to int": MapTest[float64, int]{Input: 42, ExpectedOutput: 42},
"float64 to int8": MapTest[float64, int8]{Input: 42, ExpectedOutput: 42},
"float64 to int16": MapTest[float64, int16]{Input: 42, ExpectedOutput: 42},
"float64 to int32": MapTest[float64, int32]{Input: 42, ExpectedOutput: 42},
"float64 to int64": MapTest[float64, int64]{Input: 42, ExpectedOutput: 42},
"float64 to uint": MapTest[float64, uint]{Input: 42, ExpectedOutput: 42},
"float64 to uint8": MapTest[float64, uint8]{Input: 42, ExpectedOutput: 42},
"float64 to uint16": MapTest[float64, uint16]{Input: 42, ExpectedOutput: 42},
"float64 to uint32": MapTest[float64, uint32]{Input: 42, ExpectedOutput: 42},
"float64 to uint64": MapTest[float64, uint64]{Input: 42, ExpectedOutput: 42},
"float64 to float32": MapTest[float64, float32]{Input: 42, ExpectedOutput: 42},
"float64 to float64": MapTest[float64, float64]{Input: 42, ExpectedOutput: 42},
} {
t.Run(name, func(t *testing.T) {
c.TestConvert(t)
})
}
for name, c := range map[string]TestableConvert{
"string integer": MapTest[string, uint]{Input: "42", ExpectedOutput: 42},
"string with spaces": MapTest[string, uint]{Input: "42 ", ExpectedOutput: 42},
"string float": MapTest[string, uint]{Input: "42.0", ExpectedOutput: 42},
"string true": MapTest[string, uint]{Input: "true", ExpectedOutput: 1},
"string false": MapTest[string, uint]{Input: "false", ExpectedOutput: 0},
"string 10_0": MapTest[string, uint]{Input: "10_0", ExpectedOutput: 100},
"string binary": MapTest[string, uint]{Input: "0b101010", ExpectedOutput: 42},
"string short octal notation": MapTest[string, uint]{Input: "042", ExpectedOutput: 34},
"string octal": MapTest[string, uint]{Input: "0o42", ExpectedOutput: 34},
"string hexadecimal": MapTest[string, uint]{Input: "0x42", ExpectedOutput: 66},
"boolean true": MapTest[bool, uint]{Input: true, ExpectedOutput: 1},
"boolean false": MapTest[bool, uint]{Input: false, ExpectedOutput: 0},
"empty string": MapTest[string, uint]{Input: "", ExpectedError: safecast.ErrStringConversion},
"simple space": MapTest[string, uint]{Input: " ", ExpectedError: safecast.ErrStringConversion},
"simple dot": MapTest[string, uint]{Input: ".", ExpectedError: safecast.ErrStringConversion},
"simple dash": MapTest[string, uint]{Input: "-", ExpectedError: safecast.ErrStringConversion},
"invalid string": MapTest[string, uint]{Input: "abc", ExpectedError: safecast.ErrStringConversion},
"invalid string with dot": MapTest[string, uint]{Input: "ab.c", ExpectedError: safecast.ErrStringConversion},
"strings with leading +": MapTest[string, uint]{Input: "+42", ExpectedError: safecast.ErrStringConversion},
"invalid string multiple leading dashes": MapTest[string, uint]{Input: "--42", ExpectedError: safecast.ErrStringConversion},
"invalid string with dash": MapTest[string, uint]{Input: "-abc", ExpectedError: safecast.ErrStringConversion},
"invalid string with dash and dot": MapTest[string, uint]{Input: "-ab.c", ExpectedError: safecast.ErrStringConversion},
} {
t.Run(name, func(t *testing.T) {
c.TestConvert(t)
})
}
negativeZero := math.Copysign(0, -1)
t.Run("convert to float32 near zero", func(t *testing.T) {
for name, tt := range map[string]TestableConvert{
"negative untyped zero": MapTest[float64, float32]{Input: negativeZero, ExpectedOutput: float32(negativeZero)},
"smallest positive non-zero float32": MapTest[float64, float32]{Input: math.SmallestNonzeroFloat32, ExpectedOutput: 1e-45},
"smallest negative non-zero float32": MapTest[float64, float32]{Input: -math.SmallestNonzeroFloat32, ExpectedOutput: -1e-45},
"smallest positive non-zero float64": MapTest[float64, float32]{Input: math.SmallestNonzeroFloat64, ExpectedOutput: 4.9e-324},
"smallest negative non-zero float64": MapTest[float64, float32]{Input: -math.SmallestNonzeroFloat64, ExpectedOutput: -4.9e-324},
} {
t.Run(name, func(t *testing.T) {
tt.TestConvert(t)
})
}
})
t.Run("convert to float64 near zero", func(t *testing.T) {
for name, tt := range map[string]TestableConvert{
"negative untyped zero": MapTest[float64, float64]{Input: negativeZero, ExpectedOutput: negativeZero},
"smallest positive non-zero float32": MapTest[float64, float64]{Input: math.SmallestNonzeroFloat32, ExpectedOutput: 1.401298464324817e-45},
"smallest negative non-zero float32": MapTest[float64, float64]{Input: -math.SmallestNonzeroFloat32, ExpectedOutput: -1.401298464324817e-45},
"smallest positive non-zero float64": MapTest[float64, float64]{Input: math.SmallestNonzeroFloat64, ExpectedOutput: 4.9e-324},
"smallest negative non-zero float64": MapTest[float64, float64]{Input: -math.SmallestNonzeroFloat64, ExpectedOutput: -4.9e-324},
} {
t.Run(name, func(t *testing.T) {
tt.TestConvert(t)
})
}
})
for name, c := range map[string]TestableConvert{
"upper bound overflows for int": MapTest[uint, int]{
Input: uint(math.MaxInt + 1),
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for int8": MapTest[uint, int8]{
Input: uint(math.MaxInt8 + 1),
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for int16": MapTest[uint, int16]{
Input: uint(math.MaxInt16 + 1),
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for int32": MapTest[uint, int32]{
Input: uint(math.MaxInt32 + 1),
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for int64": MapTest[float64, int64]{
Input: float64(math.MaxInt64 * 1.01), // using float64 here avoid issue when testing on 32-bit systems
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for uint": MapTest[float64, uint]{
Input: float64(math.MaxUint * 1.01), // using float64 here avoid issue when testing on 32-bit systems
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for uint8": MapTest[uint, uint8]{
Input: uint(math.MaxUint8 + 1),
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for uint16": MapTest[uint, uint16]{
Input: uint(math.MaxUint16 + 1),
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for uint32": MapTest[float64, uint32]{
Input: float64(math.MaxUint32 * 1.01),
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for uint64": MapTest[float64, uint64]{
Input: float64(math.MaxUint64 * 1.01),
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for int string": MapTest[string, int]{
Input: "9223372036854775808", // math.MaxInt64 + 1
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for int8 string": MapTest[string, int8]{
Input: "129", // math.MaxInt8 + 1
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for int16 string": MapTest[string, int16]{
Input: "32769", // math.MaxInt16 + 1
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for int32 string": MapTest[string, int32]{
Input: "2147483648", // math.MaxInt32 + 1
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for int64 string": MapTest[string, int64]{
Input: "9223372036854775808", // math.MaxInt64 + 1
ExpectedError: safecast.ErrExceedMaximumValue,
},
"upper bound overflows for int64 string overflow": MapTest[string, int64]{
Input: "123456789012345678901234567890", // more characters than math.MaxInt64 represented as string
ExpectedError: safecast.ErrExceedMaximumValue,
},
} {
t.Run(name, func(t *testing.T) {
c.TestConvert(t)
})
}
for name, c := range map[string]TestableConvert{
"lower bound overflows for int": MapTest[float64, int]{
Input: float64(math.MinInt * 1.01), // the float64 conversion is used to avoid overflow on 32-bit
ExpectedError: safecast.ErrExceedMinimumValue,
},
"lower bound overflows for int8": MapTest[int, int8]{
Input: math.MinInt8 - 1,
ExpectedError: safecast.ErrExceedMinimumValue,
},
"lower bound overflows for int16": MapTest[int, int16]{
Input: math.MinInt16 - 1,
ExpectedError: safecast.ErrExceedMinimumValue,
},
"lower bound overflows for int32": MapTest[float64, int32]{
Input: float64(math.MinInt32 - 1), // the float64 conversion is used to avoid overflow on 32-bit,
ExpectedError: safecast.ErrExceedMinimumValue,
},
"lower bound overflows for int64": MapTest[float64, int64]{
Input: float64(math.MinInt64 * 1.01), // the float64 conversion is used to avoid overflow on 32-bit
ExpectedError: safecast.ErrExceedMinimumValue,
},
"lower bound overflows for float32": MapTest[float64, float32]{
Input: -float64(math.MaxFloat32 * 1.01),
ExpectedError: safecast.ErrExceedMinimumValue,
},
// Note: float64 cannot overflow
"negative overflows uint": MapTest[int, uint]{
Input: -42,
ExpectedError: safecast.ErrExceedMinimumValue,
},
"negative overflows uint8": MapTest[int, uint8]{
Input: -42,
ExpectedError: safecast.ErrExceedMinimumValue,
},
"negative overflows uint16": MapTest[int, uint16]{
Input: -42,
ExpectedError: safecast.ErrExceedMinimumValue,
},
"negative overflows uint32": MapTest[int, uint32]{
Input: -42,
ExpectedError: safecast.ErrExceedMinimumValue,
},
"negative overflows uint64": MapTest[int, uint64]{
Input: -42,
ExpectedError: safecast.ErrExceedMinimumValue,
},
"lower bound overflows int from string": MapTest[string, int]{
Input: "-9223372036854775809", // math.MinInt64 - 1
ExpectedError: safecast.ErrExceedMinimumValue,
},
"lower bound overflows int8 from string": MapTest[string, int8]{
Input: "-129", // math.MinInt8 - 1
ExpectedError: safecast.ErrExceedMinimumValue,
},
"lower bound overflows int16 from string": MapTest[string, int16]{
Input: "-32769", // math.MinInt16 - 1
ExpectedError: safecast.ErrExceedMinimumValue,
},
"lower bound overflows int32 from string": MapTest[string, int32]{
Input: "-2147483649", // math.MinInt32 - 1
ExpectedError: safecast.ErrExceedMinimumValue,
},
"lower bound overflows int64 from string": MapTest[string, int64]{
Input: "-9223372036854775809", // math.MinInt64 - 1
ExpectedError: safecast.ErrExceedMinimumValue,
},
"lower bound overflows int64 from string overflow": MapTest[string, int64]{
Input: "-123456789012345678901234567890", // more characters than math.MinInt64 represented as string
ExpectedError: safecast.ErrExceedMinimumValue,
},
"negative string overflows uint": MapTest[string, uint]{
Input: "-1",
ExpectedError: safecast.ErrExceedMinimumValue,
},
"negative string overflows uint8": MapTest[string, uint8]{
Input: "-1",
ExpectedError: safecast.ErrExceedMinimumValue,
},
"negative string overflows uint16": MapTest[string, uint16]{
Input: "-1",
ExpectedError: safecast.ErrExceedMinimumValue,
},
"negative string overflows uint32": MapTest[string, uint32]{
Input: "-1",
ExpectedError: safecast.ErrExceedMinimumValue,
},
"negative string overflows uint64": MapTest[string, uint64]{
Input: "-1",
ExpectedError: safecast.ErrExceedMinimumValue,
},
} {
t.Run(name, func(t *testing.T) {
c.TestConvert(t)
})
}
t.Run("aliases", func(t *testing.T) {
// Type aliases are handled separately
type (
// UintSimpleAlias is an alias
UintSimpleAlias = uint
// UintTypeAlias is a type alias
UintTypeAlias uint
// StringAlias is an alias
StringAlias = string
// StringTypeAlias is a type alias
StringTypeAlias string
// BoolAlias is an alias
BoolAlias = bool
// BoolTypeAlias is a type alias
BoolTypeAlias bool
)
for name, c := range map[string]TestableConvert{
"integer simple alias": MapTest[UintSimpleAlias, int8]{
Input: UintSimpleAlias(42),
ExpectedOutput: int8(42),
},
"integer type alias": MapTest[UintTypeAlias, int8]{
Input: UintTypeAlias(42),
ExpectedOutput: int8(42),
},
"string simple alias": MapTest[StringAlias, int8]{
Input: StringAlias("42"),
ExpectedOutput: int8(42),
},
"string type alias": MapTest[StringTypeAlias, int8]{
Input: StringTypeAlias("42"),
ExpectedOutput: int8(42),
},
"bool simple alias": MapTest[BoolAlias, int8]{
Input: BoolAlias(true),
ExpectedOutput: int8(1),
},
"bool type alias": MapTest[BoolTypeAlias, int8]{
Input: BoolTypeAlias(true),
ExpectedOutput: int8(1),
},
"simple alias overflows for int8": MapTest[UintSimpleAlias, int8]{
Input: UintSimpleAlias(255),
ExpectedError: safecast.ErrExceedMaximumValue,
ErrorContains: "255 (uint) is greater than 127 (int8)",
},
"type alias overflows for int8": MapTest[UintTypeAlias, int8]{
Input: UintTypeAlias(255),
ExpectedError: safecast.ErrExceedMaximumValue,
ErrorContains: "255 (uint) is greater than 127 (int8)",
},
} {
t.Run(name, func(t *testing.T) {
c.TestConvert(t)
})
}
})
}
type MapMustConvertTest[TypeInput safecast.Input, TypeOutput safecast.Number] struct {
Input TypeInput
ExpectedOutput TypeOutput
ExpectedError error
}
func (mt MapMustConvertTest[I, O]) TestConvert(t *testing.T) {
defer func(t *testing.T) {
t.Helper()
r := recover()
if mt.ExpectedError == nil && r == nil {
return
}
if r == nil {
t.Fatal("did not panic")
}
err, ok := r.(error)
if !ok {
t.Fatalf("panic value is not an error: %v", r)
}
if !errors.Is(err, safecast.ErrConversionIssue) {
t.Fatalf("panic with unexpected error: %v", err)
}
if !errors.Is(err, mt.ExpectedError) {
t.Fatalf("panic with unexpected error: %v", err)
}
}(t)
out := safecast.MustConvert[O](mt.Input)
assertEqual(t, mt.ExpectedOutput, out)
}
func TestMustConvert(t *testing.T) {
// [TestConvert] tested all the cases
// here we are simply checking that the function panic on errors
t.Run("panic on error", func(t *testing.T) {
for name, tt := range map[string]TestableConvert{
"negative": MapMustConvertTest[int, uint8]{Input: -1, ExpectedError: safecast.ErrExceedMinimumValue},
"overflow": MapMustConvertTest[int, uint8]{Input: math.MaxInt, ExpectedError: safecast.ErrExceedMaximumValue},
"string": MapMustConvertTest[string, uint8]{Input: "cats", ExpectedError: safecast.ErrStringConversion},
} {
t.Run(name, func(t *testing.T) {
tt.TestConvert(t)
})
}
})
t.Run("no panic", func(t *testing.T) {
for name, tt := range map[string]TestableConvert{
"number": MapMustConvertTest[int, uint8]{Input: 42, ExpectedOutput: 42},
"string": MapMustConvertTest[string, uint8]{Input: "42", ExpectedOutput: 42},
"float": MapMustConvertTest[float64, uint8]{Input: 42.0, ExpectedOutput: 42},
"octal": MapMustConvertTest[string, uint8]{Input: "0o52", ExpectedOutput: 42},
} {
t.Run(name, func(t *testing.T) {
tt.TestConvert(t)
})
}
})
}
go-safecast-1.6.0/doc.go 0000664 0000000 0000000 00000000464 14764332721 0015023 0 ustar 00root root 0000000 0000000 package safecast
// Package go-safecast solves the type conversion issues in Go
//
// In Go, integer type conversion can lead to unexpected behavior and errors if not handled carefully.
// Issues can happen when converting between signed and unsigned integers, or when converting to a smaller integer type.
go-safecast-1.6.0/errors.go 0000664 0000000 0000000 00000004027 14764332721 0015571 0 ustar 00root root 0000000 0000000 package safecast
import (
"errors"
"fmt"
)
// ErrConversionIssue is a generic error for type conversion issues
// It is used to wrap other errors
var ErrConversionIssue = errors.New("conversion issue")
// ErrRangeOverflow is an error for when the value is outside the range of the desired type
var ErrRangeOverflow = errors.New("range overflow")
// ErrExceedMaximumValue is an error for when the value is greater than the maximum value of the desired type.
var ErrExceedMaximumValue = errors.New("maximum value for this type exceeded")
// ErrExceedMaximumValue is an error for when the value is less than the minimum value of the desired type.
var ErrExceedMinimumValue = errors.New("minimum value for this type exceeded")
// ErrUnsupportedConversion is an error for when the conversion is not supported from the provided type.
var ErrUnsupportedConversion = errors.New("unsupported type")
// ErrStringConversion is an error for when the conversion fails from string.
var ErrStringConversion = errors.New("cannot convert from string")
// errorHelper is a helper struct for error messages
// It is used to wrap other errors, and provides additional information
type errorHelper struct {
value any
boundary any
err error
}
func (e errorHelper) Error() string {
errMessage := ErrConversionIssue.Error()
switch {
case errors.Is(e.err, ErrExceedMaximumValue):
errMessage = fmt.Sprintf("%s: %v (%T) is greater than %v (%T)", errMessage, e.value, e.value, e.boundary, e.boundary)
case errors.Is(e.err, ErrExceedMinimumValue):
errMessage = fmt.Sprintf("%s: %v (%T) is less than %v (%T)", errMessage, e.value, e.value, e.boundary, e.boundary)
}
if e.err != nil {
errMessage = fmt.Sprintf("%s: %s", errMessage, e.err.Error())
}
return errMessage
}
func (e errorHelper) Unwrap() []error {
errs := []error{ErrConversionIssue}
if e.err != nil {
switch {
case
errors.Is(e.err, ErrExceedMaximumValue),
errors.Is(e.err, ErrExceedMinimumValue):
errs = append(errs, ErrRangeOverflow)
}
errs = append(errs, e.err)
}
return errs
}
go-safecast-1.6.0/examples_32bit_test.go 0000664 0000000 0000000 00000001737 14764332721 0020142 0 ustar 00root root 0000000 0000000 //go:build i386 || arm
package safecast_test
// The tests in examples_test.go are the ones that are not architecture dependent
// The tests in examples_32bit_test.go are for 32-bit systems
// The tests in examples_64bit_test.go are for 64-bit systems
//
// The architecture dependent files cover the difference when dealing with [safecast.ToInt]
// The error message is different on 32-bit and 64-bit systems
// The max is 9223372036854775807 on 64-bit and 2147483647 on 32-bit
//
// The examples could have been skipped for 32-bit systems,
// but I wanted the Examples to be launched on this architecture.
import (
"fmt"
"math"
"github.com/ccoveille/go-safecast"
)
func ExampleToInt() {
a := uint64(42)
i, err := safecast.ToInt(a)
fmt.Println(i, err)
b := float32(math.MaxFloat32)
i, err = safecast.ToInt(b)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: 3.4028235e+38 (float32) is greater than 2147483647 (int): maximum value for this type exceeded
}
go-safecast-1.6.0/examples_64bit_test.go 0000664 0000000 0000000 00000001750 14764332721 0020142 0 ustar 00root root 0000000 0000000 //go:build !386 && !arm
package safecast_test
// The tests in examples_test.go are the ones that are not architecture dependent
// The tests in examples_32bit_test.go are for 32-bit systems
// The tests in examples_64bit_test.go are for 64-bit systems
//
// The architecture dependent files cover the difference when dealing with [safecast.ToInt]
// The error message is different on 32-bit and 64-bit systems
// The max is 9223372036854775807 on 64-bit and 2147483647 on 32-bit
//
// The examples could have been skipped for 32-bit systems,
// but I wanted the Examples to be launched on this architecture.
import (
"fmt"
"math"
"github.com/ccoveille/go-safecast"
)
func ExampleToInt() {
a := uint64(42)
i, err := safecast.ToInt(a)
fmt.Println(i, err)
b := float32(math.MaxFloat32)
i, err = safecast.ToInt(b)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: 3.4028235e+38 (float32) is greater than 9223372036854775807 (int): maximum value for this type exceeded
}
go-safecast-1.6.0/examples_test.go 0000664 0000000 0000000 00000012201 14764332721 0017123 0 ustar 00root root 0000000 0000000 package safecast_test
import (
"fmt"
"math"
"github.com/ccoveille/go-safecast"
)
// The tests in examples_test.go are the ones that are not architecture dependent
// The tests in examples_32bit_test.go are for 32-bit systems
// The tests in examples_64bit_test.go are for 64-bit systems
//
// The architecture dependent files cover the difference when dealing with [safecast.ToInt]
// The error message is different on 32-bit and 64-bit systems
// The max is 9223372036854775807 on 64-bit and 2147483647 on 32-bit
//
// The examples could have been skipped for 32-bit systems,
// but I wanted the Examples to be launched on this architecture.
func ExampleToInt8() {
a := float64(42.42)
i, err := safecast.ToInt8(a)
fmt.Println(i, err)
a = float64(200.42)
i, err = safecast.ToInt8(a)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: 200.42 (float64) is greater than 127 (int8): maximum value for this type exceeded
}
func ExampleToUint8() {
a := int64(42)
i, err := safecast.ToUint8(a)
fmt.Println(i, err)
a = int64(-1)
i, err = safecast.ToUint8(a)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: -1 (int64) is less than 0 (uint8): minimum value for this type exceeded
}
func ExampleToInt16() {
a := int32(42)
i, err := safecast.ToInt16(a)
fmt.Println(i, err)
a = int32(40000)
i, err = safecast.ToInt16(a)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: 40000 (int32) is greater than 32767 (int16): maximum value for this type exceeded
}
func ExampleToUint16() {
a := int64(42)
i, err := safecast.ToUint16(a)
fmt.Println(i, err)
a = int64(-1)
i, err = safecast.ToUint16(a)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: -1 (int64) is less than 0 (uint16): minimum value for this type exceeded
}
func ExampleToInt32() {
a := int(42)
i, err := safecast.ToInt32(a)
fmt.Println(i, err)
b := uint32(math.MaxInt32) + 1
i, err = safecast.ToInt32(b)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: 2147483648 (uint32) is greater than 2147483647 (int32): maximum value for this type exceeded
}
func ExampleToUint32() {
a := int16(42)
i, err := safecast.ToUint32(a)
fmt.Println(i, err)
a = int16(-1)
i, err = safecast.ToUint32(a)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: -1 (int16) is less than 0 (uint32): minimum value for this type exceeded
}
func ExampleToInt64() {
a := uint64(42)
i, err := safecast.ToInt64(a)
fmt.Println(i, err)
a = uint64(math.MaxInt64) + 1
i, err = safecast.ToInt64(a)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: 9223372036854775808 (uint64) is greater than 9223372036854775807 (int64): maximum value for this type exceeded
}
func ExampleToUint64() {
a := int8(42)
i, err := safecast.ToUint64(a)
fmt.Println(i, err)
a = int8(-1)
i, err = safecast.ToUint64(a)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: -1 (int8) is less than 0 (uint64): minimum value for this type exceeded
}
func ExampleToUint() {
a := int8(42)
i, err := safecast.ToUint(a)
fmt.Println(i, err)
a = int8(-1)
i, err = safecast.ToUint(a)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: -1 (int8) is less than 0 (uint): minimum value for this type exceeded
}
func ExampleToFloat32() {
a := int8(42)
i, err := safecast.ToFloat32(a)
fmt.Println(i, err)
b := math.MaxFloat64
i, err = safecast.ToFloat32(b)
fmt.Println(i, err)
// Output:
// 42
// 0 conversion issue: 1.7976931348623157e+308 (float64) is greater than 3.4028235e+38 (float32): maximum value for this type exceeded
}
func ExampleToFloat64() {
a := int8(42)
i, err := safecast.ToFloat64(a)
fmt.Println(i, err)
b := math.MaxFloat64
i, err = safecast.ToFloat64(b)
fmt.Println(i, err)
// Output:
// 42
// 1.7976931348623157e+308
}
func ExampleConvert() {
b, err := safecast.Convert[int8](true)
fmt.Println(b, err)
b, err = safecast.Convert[int8]("true")
fmt.Println(b, err)
c, err := safecast.Convert[int16](17.1)
fmt.Println(c, err)
c, err = safecast.Convert[int16](int64(17))
fmt.Println(c, err)
d, err := safecast.Convert[int32]("17.1")
fmt.Println(d, err)
i, err := safecast.Convert[uint]("100_000")
fmt.Println(i, err)
i, err = safecast.Convert[uint]("0b11")
fmt.Println(i, err)
i, err = safecast.Convert[uint]("0x11")
fmt.Println(i, err)
a := int8(-1)
i, err = safecast.Convert[uint](a)
fmt.Println(i, err)
i, err = safecast.Convert[uint]("-1")
fmt.Println(i, err)
i, err = safecast.Convert[uint]("abc")
fmt.Println(i, err)
i, err = safecast.Convert[uint]("-1.1")
fmt.Println(i, err)
i, err = safecast.Convert[uint](".12345E+5")
fmt.Println(i, err)
// Output:
// 1
// 1
// 17
// 17
// 17
// 100000
// 3
// 17
// 0 conversion issue: -1 (int8) is less than 0 (uint): minimum value for this type exceeded
// 0 conversion issue: -1 (int64) is less than 0 (uint): minimum value for this type exceeded
// 0 conversion issue: cannot convert from string abc to uint
// 0 conversion issue: -1.1 (float64) is less than 0 (uint): minimum value for this type exceeded
// 12345
}
go-safecast-1.6.0/go.mod 0000664 0000000 0000000 00000000143 14764332721 0015027 0 ustar 00root root 0000000 0000000 module github.com/ccoveille/go-safecast
go 1.21
retract (
v1.0.0 // Published accidentally.
) go-safecast-1.6.0/go.sum 0000664 0000000 0000000 00000000000 14764332721 0015044 0 ustar 00root root 0000000 0000000 go-safecast-1.6.0/readme_test.go 0000664 0000000 0000000 00000001605 14764332721 0016550 0 ustar 00root root 0000000 0000000 package safecast_test
import (
"fmt"
"github.com/ccoveille/go-safecast"
)
func Example() {
var a int
a = 42
b, err := safecast.ToUint8(a) // everything is fine
if err != nil {
fmt.Println(err)
}
fmt.Println(b)
a = 255 + 1
_, err = safecast.ToUint8(a) // 256 is greater than uint8 maximum value
if err != nil {
fmt.Println(err)
}
a = -1
_, err = safecast.ToUint8(a) // -1 cannot fit in uint8
if err != nil {
fmt.Println(err)
}
str := "\x99" // ASCII code 153 for Trademark symbol
e := str[0]
_, err = safecast.ToInt8(e)
if err != nil {
fmt.Println(err)
}
// Output:
// 42
// conversion issue: 256 (int) is greater than 255 (uint8): maximum value for this type exceeded
// conversion issue: -1 (int) is less than 0 (uint8): minimum value for this type exceeded
// conversion issue: 153 (uint8) is greater than 127 (int8): maximum value for this type exceeded
}
go-safecast-1.6.0/types.go 0000664 0000000 0000000 00000002055 14764332721 0015420 0 ustar 00root root 0000000 0000000 package safecast
// This files is highly inspired from https://pkg.go.dev/golang.org/x/exp/constraints
// I didn't import it as it would have added an unnecessary dependency
// Signed is a constraint for all signed integers: int, int8, int16, int32, and int64 types.
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Unsigned is a constraint for all unsigned integers: uint, uint8, uint16, uint32, and uint64 types.
// TODO: support uintpr
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
// Integer is a constraint for the all unsigned and signed integers
type Integer interface {
Signed | Unsigned
}
// Float is a constraint for the float32 and float64 types.
type Float interface {
~float32 | ~float64
}
// Number is a constraint for all integers and floats
// TODO: consider using complex, but not sure there is a need
type Number interface {
Integer | Float
}
// Input is a constraint for all types that can be used as input for [Convert], and [MustConvert]
type Input interface {
Number | ~string | ~bool
}