350 lines
8.1 KiB
Go
350 lines
8.1 KiB
Go
// Package parth provides path parsing for segment unmarshaling and slicing. In
|
|
// other words, parth provides simple and flexible access to (URL) path
|
|
// parameters.
|
|
//
|
|
// Along with string, all basic non-alias types are supported. An interface is
|
|
// available for implementation by user-defined types. When handling an int,
|
|
// uint, or float of any size, the first valid value within the specified
|
|
// segment will be used.
|
|
package parth
|
|
|
|
import (
|
|
"errors"
|
|
)
|
|
|
|
// Unmarshaler is the interface implemented by types that can unmarshal a path
|
|
// segment representation of themselves. It is safe to assume that the segment
|
|
// data will not include slashes.
|
|
type Unmarshaler interface {
|
|
UnmarshalSegment(string) error
|
|
}
|
|
|
|
// Err{Name} values facilitate error identification.
|
|
var (
|
|
ErrUnknownType = errors.New("unknown type provided")
|
|
|
|
ErrFirstSegNotFound = errors.New("first segment not found by index")
|
|
ErrLastSegNotFound = errors.New("last segment not found by index")
|
|
ErrSegOrderReversed = errors.New("first segment must precede last segment")
|
|
ErrKeySegNotFound = errors.New("segment not found by key")
|
|
|
|
ErrDataUnparsable = errors.New("data cannot be parsed")
|
|
)
|
|
|
|
// Segment locates the path segment indicated by the index i and unmarshals it
|
|
// into the provided type v. If the index is negative, the negative count
|
|
// begins with the last segment. An error is returned if: 1. The type is not a
|
|
// pointer to an instance of one of the basic non-alias types and does not
|
|
// implement the Unmarshaler interface; 2. The index is out of range of the
|
|
// path; 3. The located path segment data cannot be parsed as the provided type
|
|
// or if an error is returned when using a provided Unmarshaler implementation.
|
|
func Segment(path string, i int, v interface{}) error { //nolint
|
|
var err error
|
|
|
|
switch v := v.(type) {
|
|
case *bool:
|
|
*v, err = segmentToBool(path, i)
|
|
|
|
case *float32:
|
|
var f float64
|
|
f, err = segmentToFloatN(path, i, 32)
|
|
*v = float32(f)
|
|
|
|
case *float64:
|
|
*v, err = segmentToFloatN(path, i, 64)
|
|
|
|
case *int:
|
|
var n int64
|
|
n, err = segmentToIntN(path, i, 0)
|
|
*v = int(n)
|
|
|
|
case *int16:
|
|
var n int64
|
|
n, err = segmentToIntN(path, i, 16)
|
|
*v = int16(n)
|
|
|
|
case *int32:
|
|
var n int64
|
|
n, err = segmentToIntN(path, i, 32)
|
|
*v = int32(n)
|
|
|
|
case *int64:
|
|
*v, err = segmentToIntN(path, i, 64)
|
|
|
|
case *int8:
|
|
var n int64
|
|
n, err = segmentToIntN(path, i, 8)
|
|
*v = int8(n)
|
|
|
|
case *string:
|
|
*v, err = segmentToString(path, i)
|
|
|
|
case *uint:
|
|
var n uint64
|
|
n, err = segmentToUintN(path, i, 0)
|
|
*v = uint(n)
|
|
|
|
case *uint16:
|
|
var n uint64
|
|
n, err = segmentToUintN(path, i, 16)
|
|
*v = uint16(n)
|
|
|
|
case *uint32:
|
|
var n uint64
|
|
n, err = segmentToUintN(path, i, 32)
|
|
*v = uint32(n)
|
|
|
|
case *uint64:
|
|
*v, err = segmentToUintN(path, i, 64)
|
|
|
|
case *uint8:
|
|
var n uint64
|
|
n, err = segmentToUintN(path, i, 8)
|
|
*v = uint8(n)
|
|
|
|
case Unmarshaler:
|
|
var s string
|
|
s, err = segmentToString(path, i)
|
|
if err == nil {
|
|
err = v.UnmarshalSegment(s)
|
|
}
|
|
|
|
default:
|
|
err = ErrUnknownType
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Sequent is similar to Segment, but uses a key to locate a segment and then
|
|
// unmarshal the subsequent segment. It is a simple wrapper over SubSeg with an
|
|
// index of 0.
|
|
func Sequent(path, key string, v interface{}) error {
|
|
return SubSeg(path, key, 0, v)
|
|
}
|
|
|
|
// Span returns the path segments between two segment indexes i and j including
|
|
// the first segment. If an index is negative, the negative count begins with
|
|
// the last segment. Providing a 0 for the last index j is a special case which
|
|
// acts as an alias for the end of the path. If the first segment does not begin
|
|
// with a slash and it is part of the requested span, no slash will be added. An
|
|
// error is returned if: 1. Either index is out of range of the path; 2. The
|
|
// first index i does not precede the last index j.
|
|
func Span(path string, i, j int) (string, error) {
|
|
var f, l int
|
|
var ok bool
|
|
|
|
if i < 0 {
|
|
f, ok = segStartIndexFromEnd(path, i)
|
|
} else {
|
|
f, ok = segStartIndexFromStart(path, i)
|
|
}
|
|
if !ok {
|
|
return "", ErrFirstSegNotFound
|
|
}
|
|
|
|
if j > 0 {
|
|
l, ok = segEndIndexFromStart(path, j)
|
|
} else {
|
|
l, ok = segEndIndexFromEnd(path, j)
|
|
}
|
|
if !ok {
|
|
return "", ErrLastSegNotFound
|
|
}
|
|
|
|
if f == l {
|
|
return "", nil
|
|
}
|
|
|
|
if f > l {
|
|
return "", ErrSegOrderReversed
|
|
}
|
|
|
|
return path[f:l], nil
|
|
}
|
|
|
|
// SubSeg is similar to Segment, but only handles the portion of the path
|
|
// subsequent to the provided key. For example, to access the segment
|
|
// immediately after a key, an index of 0 should be provided (see Sequent). An
|
|
// error is returned if the key cannot be found in the path.
|
|
func SubSeg(path, key string, i int, v interface{}) error { //nolint
|
|
var err error
|
|
|
|
switch v := v.(type) {
|
|
case *bool:
|
|
*v, err = subSegToBool(path, key, i)
|
|
|
|
case *float32:
|
|
var f float64
|
|
f, err = subSegToFloatN(path, key, i, 32)
|
|
*v = float32(f)
|
|
|
|
case *float64:
|
|
*v, err = subSegToFloatN(path, key, i, 64)
|
|
|
|
case *int:
|
|
var n int64
|
|
n, err = subSegToIntN(path, key, i, 0)
|
|
*v = int(n)
|
|
|
|
case *int16:
|
|
var n int64
|
|
n, err = subSegToIntN(path, key, i, 16)
|
|
*v = int16(n)
|
|
|
|
case *int32:
|
|
var n int64
|
|
n, err = subSegToIntN(path, key, i, 32)
|
|
*v = int32(n)
|
|
|
|
case *int64:
|
|
*v, err = subSegToIntN(path, key, i, 64)
|
|
|
|
case *int8:
|
|
var n int64
|
|
n, err = subSegToIntN(path, key, i, 8)
|
|
*v = int8(n)
|
|
|
|
case *string:
|
|
*v, err = subSegToString(path, key, i)
|
|
|
|
case *uint:
|
|
var n uint64
|
|
n, err = subSegToUintN(path, key, i, 0)
|
|
*v = uint(n)
|
|
|
|
case *uint16:
|
|
var n uint64
|
|
n, err = subSegToUintN(path, key, i, 16)
|
|
*v = uint16(n)
|
|
|
|
case *uint32:
|
|
var n uint64
|
|
n, err = subSegToUintN(path, key, i, 32)
|
|
*v = uint32(n)
|
|
|
|
case *uint64:
|
|
*v, err = subSegToUintN(path, key, i, 64)
|
|
|
|
case *uint8:
|
|
var n uint64
|
|
n, err = subSegToUintN(path, key, i, 8)
|
|
*v = uint8(n)
|
|
|
|
case Unmarshaler:
|
|
var s string
|
|
s, err = subSegToString(path, key, i)
|
|
if err == nil {
|
|
err = v.UnmarshalSegment(s)
|
|
}
|
|
|
|
default:
|
|
err = ErrUnknownType
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// SubSpan is similar to Span, but only handles the portion of the path
|
|
// subsequent to the provided key. An error is returned if the key cannot be
|
|
// found in the path.
|
|
func SubSpan(path, key string, i, j int) (string, error) {
|
|
si, ok := segIndexByKey(path, key)
|
|
if !ok {
|
|
return "", ErrKeySegNotFound
|
|
}
|
|
|
|
if i >= 0 {
|
|
i++
|
|
}
|
|
if j > 0 {
|
|
j++
|
|
}
|
|
|
|
s, err := Span(path[si:], i, j)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// Parth manages path and error data for processing a single path multiple
|
|
// times while error checking only once. Only the first encountered error is
|
|
// stored as all subsequent calls to Parth methods that can error are elided.
|
|
type Parth struct {
|
|
path string
|
|
err error
|
|
}
|
|
|
|
// New constructs a pointer to an instance of Parth around the provided path.
|
|
func New(path string) *Parth {
|
|
return &Parth{path: path}
|
|
}
|
|
|
|
// NewBySpan constructs a pointer to an instance of Parth after preprocessing
|
|
// the provided path with Span.
|
|
func NewBySpan(path string, i, j int) *Parth {
|
|
s, err := Span(path, i, j)
|
|
return &Parth{s, err}
|
|
}
|
|
|
|
// NewBySubSpan constructs a pointer to an instance of Parth after
|
|
// preprocessing the provided path with SubSpan.
|
|
func NewBySubSpan(path, key string, i, j int) *Parth {
|
|
s, err := SubSpan(path, key, i, j)
|
|
return &Parth{s, err}
|
|
}
|
|
|
|
// Err returns the first error encountered by the *Parth receiver.
|
|
func (p *Parth) Err() error {
|
|
return p.err
|
|
}
|
|
|
|
// Segment operates the same as the package-level function Segment.
|
|
func (p *Parth) Segment(i int, v interface{}) {
|
|
if p.err != nil {
|
|
return
|
|
}
|
|
|
|
p.err = Segment(p.path, i, v)
|
|
}
|
|
|
|
// Sequent operates the same as the package-level function Sequent.
|
|
func (p *Parth) Sequent(key string, v interface{}) {
|
|
p.SubSeg(key, 0, v)
|
|
}
|
|
|
|
// Span operates the same as the package-level function Span.
|
|
func (p *Parth) Span(i, j int) string {
|
|
if p.err != nil {
|
|
return ""
|
|
}
|
|
|
|
s, err := Span(p.path, i, j)
|
|
p.err = err
|
|
|
|
return s
|
|
}
|
|
|
|
// SubSeg operates the same as the package-level function SubSeg.
|
|
func (p *Parth) SubSeg(key string, i int, v interface{}) {
|
|
if p.err != nil {
|
|
return
|
|
}
|
|
|
|
p.err = SubSeg(p.path, key, i, v)
|
|
}
|
|
|
|
// SubSpan operates the same as the package-level function SubSpan.
|
|
func (p *Parth) SubSpan(key string, i, j int) string {
|
|
if p.err != nil {
|
|
return ""
|
|
}
|
|
|
|
s, err := SubSpan(p.path, key, i, j)
|
|
p.err = err
|
|
|
|
return s
|
|
}
|