stringable

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 20, 2024 License: MIT Imports: 6 Imported by: 0

README

stringable

A tiny go package that helps converting values from/to a string.

Go codecov Go Report Card Go Reference

Basic API

var yesno bool
sb, err := stringable.New(&yesno)

sb.FromString("true")
sb.ToString()

Supported Builtin Types

  • string, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128
  • time.Time
  • []byte

The Hybrid Stringable Instance

When calling stringable.New(x) with an instance x that is not a Stringable itself, nor any of the above builtin types, it will try to create a "hybrid" Stringable instance from x for you.

Here is how the "hybrid" Stringable instance will be created:

  1. Create a hybrid instance h from the given instance x;
  2. If x has implemented one of stringable.StringMarshaler and encoding.TextMarshaler, h will use it as the implementation of stringable.StringMarshaler, i.e. the ToString() method;
  3. If x has implemented one of stringable.StringUnmarshaler and encoding.TextUnmarshaler, h will use it as the implementation of stringable.StringUnmarshaler, i.e. the FromString() method;
  4. As long as h has an implementation of either stringable.StringMarshaler or stringable.StringUnmarshaler, we consider h is a valid Stringable instance. You can require both by passing in a CompleteHybrid() option to New method. For a valid h, stringable.New(x) will return h. Otherwise, an ErrUnsupportedType occurs.

Example:

type Location struct {
	X int
	Y int
}

func (l *Location) MarshalText() ([]byte, error) {
	return []byte(fmt.Sprintf("L(%d,%d)", l.X, l.Y)), nil
}

loc := &Location{3, 4}
sb, err := stringable.New(loc) // err is nil

sb.ToString() // L(3,4)
sb.FromString("L(5,6)") // ErrNotStringUnmarshaler, "not a StringUnmarshaler"
Hybrid Options
  1. New(v, NoHybrid()): prevent New from trying to create a hybrid instance from v at all. Instead, returns ErrUnsupportedType.
  2. New(v, CompleteHybrid()): still allow New trying to create a hybrid instance from v if necessary, but with the present of CompleteHybrid() option, the returned hybrid instance must have a valid implementation of both FromString and ToString.

Adapt/Override Existing Types

The Namespace.Adapt() API is used to customize the behaviour of stringable.Stringable of a specific type. The principal is to create a type alias to the target type you want to override, and implement the Stringable interface on the new type.

When should you use this API?

  1. change the conversion logic of the builtin types.
  2. change the conversion logic of existing types that are "hybridizable", but you don't want to change their implementations.

For example, the default support of bool type in this package uses strconv.ParseBool method to convert strings like "true", "TRUE", "f", "0", etc. to a bool value. If you want to support also converting "YES", "NO", "はい" to a bool value, you can implement a custom bool type and register it to a Namespace instance:

type YesNo bool

func (yn YesNo) ToString() (string, error) {
	if yn {
		return "yes", nil
	} else {
		return "no", nil
	}
}

func (yn *YesNo) FromString(s string) error {
	switch strings.ToLower(s) {
	case "yes":
		*yn = true
	case "no":
		*yn = false
	default:
		return errors.New("invalid value")
	}
	return nil
}

func main() {
	ns := stringable.NewNamespace()
	typ, adaptor := ToAnyStringableAdaptor(func(b *bool) (Stringable, error) {
		return (*YesNo)(b), nil
	})
	ns.Adapt(typ, adaptor)

	var yesno bool = true
	sb, err := ns.New(&yesno)
}

Documentation

Overview

stringable is a tiny package that helps converting values from/to a string.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrUnsupportedType      = errors.New("unsupported type")
	ErrTypeMismatch         = errors.New("type mismatch")
	ErrNotStringMarshaler   = errors.New("not a StringMarshaler")
	ErrNotStringUnmarshaler = errors.New("not a StringUnmarshaler")
	ErrNotPointer           = errors.New("not a pointer")
	ErrNilPointer           = errors.New("nil pointer")
)

Functions

This section is empty.

Types

type AnyStringableAdaptor

type AnyStringableAdaptor func(any) (Stringable, error)

func ToAnyStringableAdaptor

func ToAnyStringableAdaptor[T any](adapt StringableAdaptor[T]) (reflect.Type, AnyStringableAdaptor)

type Namespace

type Namespace struct {
	// contains filtered or unexported fields
}

Namespace is the place to register type adaptors (of AnyStringableAdaptor).

func NewNamespace

func NewNamespace() *Namespace

NewNamespace creates a namespace where you can register adaptors to override/adapt the converting behaviours of existing types.

func (*Namespace) Adapt

func (c *Namespace) Adapt(typ reflect.Type, adaptor AnyStringableAdaptor)

Adapt registers a custom adaptor for the given type.

  1. You must create a Namespace instance and register the adaptor there.
  2. Call ToAnyStringableAdaptor to create an adaptor of a specific type.

Example:

ns := stringable.NewNamespace()
typ, adaptor := stringable.ToAnyStringableAdaptor[bool](func(b *bool) (stringable.Stringable, error) {
	// todo
})
ns.Adapt(typ, adaptor)

func (*Namespace) New

func (c *Namespace) New(v any, opts ...Option) (Stringable, error)

New creates a Stringable instance from the given value. If the given value itself is already a Stringable, it will return directly. Otherwise, it will try to create a Stringable instance by trying the following approaches:

  1. check if there's a custom adaptor for the type of the given value, if so, use it to adapt the given value to a Stringable.
  2. same as above, but check the builtin adaptors, which support the builtin types, e.g. int, string, float64, etc.
  3. try to create a "hybrid" instance, which makes use of the methods FromString, ToString, MarshalText and UnmarshalText to fullfill the Stringable interface.

It has three options:

New(v)

1. with only default options, it will try all the 3 ways as listed above to create a Stringable.

New(v, NoHybrid())

2. without hybrid, i.e. won't try the 3rd method, returns an ErrUnsupportedType error.

New(v, CompleteHybrid())

3. the hybrid must be a "complete" hybrid, which means it has to implement both FromString and ToString method that the Stringable interface requires, while a "partial"/"incomplete" hybrid, one of theses two methods can be absent, and the absent one always returns an error, either ErrNotStringMarshaler or ErrNotStringUnmarshaler.

type Option

type Option func(o *options)

func CompleteHybrid

func CompleteHybrid() Option

func NoHybrid

func NoHybrid() Option

type StringMarshaler

type StringMarshaler interface {
	ToString() (string, error)
}

StringMarshaler defines a type to be able to convert to a string.

type StringUnmarshaler

type StringUnmarshaler interface {
	FromString(string) error
}

StringUnmarshaler defines a type to be able to convert from a string.

type Stringable

type Stringable interface {
	StringMarshaler
	StringUnmarshaler
}

Stringable defines a type to be able to convert from/to a string.

func New

func New(v any) (Stringable, error)

New creates a Stringable instance from the given value. Note that this method is a wrapper around the default namespace's New method. Which means it doesn't support override/adapt existing types. Please read Namespace.New to learn more.

type StringableAdaptor

type StringableAdaptor[T any] func(*T) (Stringable, error)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL