mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-04 12:52:28 +08:00
@@ -1,51 +1,51 @@
|
|||||||
-----BEGIN rsa private key-----
|
-----BEGIN rsa private key-----
|
||||||
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
|
||||||
-----END rsa private key-----
|
-----END rsa private key-----
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
-----BEGIN rsa public key-----
|
-----BEGIN rsa public key-----
|
||||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA8O2hLVt/7v6rQF2eGX3J
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAudV/zW+ycOExUja9W3Zy
|
||||||
+QyteV45lI/5qlyqTe7CiwUg1bkJ2yLHaoGSWoVtthE/X7GZgsw57VEgnazZAehe
|
hKWA2TN+FqTzfZKPB+btwe4Md0WJTM0+ZdT8UXujltTEWSUhY/qkOiNIutF2CiFW
|
||||||
XcxV8OJWBGuRfmXabtKIzSBF8uZj+H3iPDvJB7NggMh8teYsdBSn9Cm/L5FesDvg
|
onDQeNzMobLB/pmq1P0Z+LVH4ERsbcl9zYCfpvTsnIqzjuPe30iozK0Er03qBxsH
|
||||||
/KxUKKfBU4LSrhOMhBauIoOSVU659y3kYgpzC/ormvCsJSe3KSdIhtLxiv+dnEHh
|
nWV3WbIl3+1f17T6OD5CkdT+9RCID1EqsQ+9aGIeR6cmoB+rxjPLb0xc5oS1hbb3
|
||||||
9WvsXWB50lIw3TIC041aL5mwRvb5/JWcjtn5bgLEChrnEGF0RYUDMwm8hT1YsEN9
|
FkiT7VLI2doeqP8Pmwdohbh7XmgJQkok+ALxKQ4bCMJ780k2KigKGjXxKlYJq1ZF
|
||||||
Rb9fOx5ZTUZNRVHYjaCyUA+cG+gC72EjrkXY4If3pAESNcF+5NGGJaETd8rHzHsR
|
301sbhvTo2cSci4ieXP0A4B4swSzLKl0G7IX/UbYACn3qNecvQt5OtFM644mqfSU
|
||||||
FRVPXkwhRmkx9nBF+RjQhouqBTptIcIO2rPlTC9H5Oeg+jVljlBJ7/ESeOlg2OEb
|
ffFg7PefZVZhaUytvU92W+b0LXF2NbjhtVES5HByDwjjF/KOfV7U/o+YmAjlakie
|
||||||
YyOUJpAPMPcz5HiNSTRHuDWIDUuk48BOF9Vo00PJq/oXRzUa9HHhu6cosfZH4P4P
|
YM7pcggfgfqyZWdF70nSvgPgVt5qtOnYPeUrQV2aUmZE+BagOQ/HAIKPbhmyMEA3
|
||||||
kDthxq89migqUL+C8WqeZ8I+xFS8/1TpvjOnOYF5ADvByC69UGPEoh+o+ZpgWfAs
|
odgqaALsvD/58iVv9syqEEm5trLYA/p2uo0yv3iHrEggEZkjPXkrgbZ6lZNafGaZ
|
||||||
Crb9Qwy9WAAZg0rsouoPqb4Qk6Vr88X6I20Ys9AayLzZ72ddoS5IweOoF5/xLfii
|
Hs0ANg+7NIJR9joKvXGJkg8E2thpg9UKl/Z2astZk7o92trMp5DQW1vjV7JW+mEQ
|
||||||
cGLKckNwG6sy4f1LCFB2Xc79nYqJzlwPGC3pKfEIAXUj1I/p+/MqaTDJQs72dhr0
|
dztQxE5NgeE8cI/BdqMSyvG2UA7u5LqBHXx35s3gPva+eutKnTcRpO3T01PH2sba
|
||||||
lTX5505XUBqrbX0BPGcZvIsCAwEAAQ==
|
iXiiNG5oFUYjocikhY7f3TUCAwEAAQ==
|
||||||
-----END rsa public key-----
|
-----END rsa public key-----
|
||||||
|
|||||||
312
enum/enum.go
Normal file
312
enum/enum.go
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
// Copyright 2025 dudaodong@gmail.com. All rights resulterved.
|
||||||
|
// Use of this source code is governed by MIT license
|
||||||
|
|
||||||
|
// Package enum provides a simple enum implementation.
|
||||||
|
package enum
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum defines a common enum interface.
|
||||||
|
type Enum[T comparable] interface {
|
||||||
|
Value() T
|
||||||
|
String() string
|
||||||
|
Name() string
|
||||||
|
Valid(checker ...func(T) bool) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item defines a common enum item struct implement Enum interface.
|
||||||
|
type Item[T comparable] struct {
|
||||||
|
value T
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewItem[T comparable](value T, name string) *Item[T] {
|
||||||
|
return &Item[T]{value: value, name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pair represents a value-name pair for creating enum items
|
||||||
|
type Pair[T comparable] struct {
|
||||||
|
Value T
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewItemsFromPairs creates enum items from a slice of Pair structs.
|
||||||
|
func NewItems[T comparable](pairs ...Pair[T]) []*Item[T] {
|
||||||
|
if len(pairs) == 0 {
|
||||||
|
return []*Item[T]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]*Item[T], 0, len(pairs))
|
||||||
|
for _, pair := range pairs {
|
||||||
|
items = append(items, &Item[T]{value: pair.Value, name: pair.Name})
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Item[T]) Value() T {
|
||||||
|
return e.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Item[T]) Name() string {
|
||||||
|
return e.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Item[T]) String() string {
|
||||||
|
return e.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid checks if the enum item is valid. If a custom check function is provided, it will be used to validate the value.
|
||||||
|
func (e *Item[T]) Valid(checker ...func(T) bool) bool {
|
||||||
|
if len(checker) > 0 {
|
||||||
|
return checker[0](e.value) && e.name != ""
|
||||||
|
}
|
||||||
|
var zero T
|
||||||
|
return e.value != zero && e.name != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (e *Item[T]) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]any{
|
||||||
|
"value": e.value,
|
||||||
|
"name": e.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (e *Item[T]) UnmarshalJSON(data []byte) error {
|
||||||
|
type alias struct {
|
||||||
|
Value any `json:"value"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
var temp alias
|
||||||
|
if err := json.Unmarshal(data, &temp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var v T
|
||||||
|
rv := reflect.TypeOf(v)
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
val, ok := temp.Value.(float64)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid type for value, want int family")
|
||||||
|
}
|
||||||
|
converted := reflect.ValueOf(int64(val)).Convert(rv)
|
||||||
|
e.value = converted.Interface().(T)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
val, ok := temp.Value.(float64)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid type for value, want uint family")
|
||||||
|
}
|
||||||
|
converted := reflect.ValueOf(uint64(val)).Convert(rv)
|
||||||
|
e.value = converted.Interface().(T)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
val, ok := temp.Value.(float64)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid type for value, want float family")
|
||||||
|
}
|
||||||
|
converted := reflect.ValueOf(val).Convert(rv)
|
||||||
|
e.value = converted.Interface().(T)
|
||||||
|
case reflect.String:
|
||||||
|
val, ok := temp.Value.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid type for value, want string")
|
||||||
|
}
|
||||||
|
e.value = any(val).(T)
|
||||||
|
case reflect.Bool:
|
||||||
|
val, ok := temp.Value.(bool)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid type for value, want bool")
|
||||||
|
}
|
||||||
|
e.value = any(val).(T)
|
||||||
|
default:
|
||||||
|
// 枚举类型底层通常是 int,可以尝试 float64->int64->底层类型
|
||||||
|
val, ok := temp.Value.(float64)
|
||||||
|
if ok {
|
||||||
|
converted := reflect.ValueOf(int64(val)).Convert(rv)
|
||||||
|
e.value = converted.Interface().(T)
|
||||||
|
} else {
|
||||||
|
val2, ok2 := temp.Value.(T)
|
||||||
|
if !ok2 {
|
||||||
|
return fmt.Errorf("invalid type for value")
|
||||||
|
}
|
||||||
|
e.value = val2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.name = temp.Name
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registry defines a common enum registry struct.
|
||||||
|
type Registry[T comparable] struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
values map[T]*Item[T]
|
||||||
|
names map[string]*Item[T]
|
||||||
|
items []*Item[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry[T comparable](items ...*Item[T]) *Registry[T] {
|
||||||
|
r := &Registry[T]{
|
||||||
|
values: make(map[T]*Item[T]),
|
||||||
|
names: make(map[string]*Item[T]),
|
||||||
|
items: make([]*Item[T], 0, len(items)),
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Add(items...)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds enum items to the registry.
|
||||||
|
func (r *Registry[T]) Add(items ...*Item[T]) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
if _, exists := r.values[item.value]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := r.names[item.name]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
r.values[item.value] = item
|
||||||
|
r.names[item.name] = item
|
||||||
|
r.items = append(r.items, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes an enum item from the registry by its value.
|
||||||
|
func (r *Registry[T]) Remove(value T) bool {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
item, ok := r.values[value]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
delete(r.values, value)
|
||||||
|
delete(r.names, item.name)
|
||||||
|
for i, it := range r.items {
|
||||||
|
if it.value == value {
|
||||||
|
r.items = append(r.items[:i], r.items[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates the name of an enum item in the registry by its value.
|
||||||
|
func (r *Registry[T]) Update(value T, newName string) bool {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
item, ok := r.values[value]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
delete(r.names, item.name)
|
||||||
|
item.name = newName
|
||||||
|
r.names[newName] = item
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByValue retrieves an enum item by its value.
|
||||||
|
func (r *Registry[T]) GetByValue(value T) (*Item[T], bool) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
item, ok := r.values[value]
|
||||||
|
return item, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByName retrieves an enum item by its name.
|
||||||
|
func (r *Registry[T]) GetByName(name string) (*Item[T], bool) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
item, ok := r.names[name]
|
||||||
|
return item, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items returns a slice of all enum items in the registry.
|
||||||
|
func (r *Registry[T]) Items() []*Item[T] {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
result := make([]*Item[T], len(r.items))
|
||||||
|
copy(result, r.items)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains checks if an enum item with the given value exists in the registry.
|
||||||
|
func (r *Registry[T]) Contains(value T) bool {
|
||||||
|
_, ok := r.GetByValue(value)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the given value is a valid enum item in the registry.
|
||||||
|
func (r *Registry[T]) Validate(value T) error {
|
||||||
|
if !r.Contains(value) {
|
||||||
|
return fmt.Errorf("invalid enum value: %v", value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAll checks if all given values are valid enum items in the registry.
|
||||||
|
func (r *Registry[T]) ValidateAll(values ...T) error {
|
||||||
|
for _, value := range values {
|
||||||
|
if err := r.Validate(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the number of enum items in the registry.
|
||||||
|
func (r *Registry[T]) Size() int {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
return len(r.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range iterates over all enum items in the registry and applies the given function.
|
||||||
|
func (r *Registry[T]) Range(fn func(*Item[T]) bool) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
for _, item := range r.items {
|
||||||
|
if !fn(item) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortedItems returns a slice of all enum items sorted by the given less function.
|
||||||
|
func (r *Registry[T]) SortedItems(less func(*Item[T], *Item[T]) bool) []*Item[T] {
|
||||||
|
items := r.Items()
|
||||||
|
sort.Slice(items, func(i, j int) bool {
|
||||||
|
return less(items[i], items[j])
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter returns a slice of enum items that satisfy the given predicate function.
|
||||||
|
func (r *Registry[T]) Filter(predicate func(*Item[T]) bool) []*Item[T] {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
|
var result []*Item[T]
|
||||||
|
for _, item := range r.items {
|
||||||
|
if predicate(item) {
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
222
enum/enum_example_test.go
Normal file
222
enum/enum_example_test.go
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
package enum
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func ExampleNewItem() {
|
||||||
|
items := NewItems(
|
||||||
|
Pair[Status]{Value: Active, Name: "Active"},
|
||||||
|
Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println(items[0].Name(), items[0].Value())
|
||||||
|
fmt.Println(items[1].Name(), items[1].Value())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Active 1
|
||||||
|
// Inactive 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleItem_Valid() {
|
||||||
|
item := NewItem(Active, "Active")
|
||||||
|
fmt.Println(item.Valid())
|
||||||
|
|
||||||
|
invalidItem := NewItem(Unknown, "")
|
||||||
|
fmt.Println(invalidItem.Valid())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// true
|
||||||
|
// false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleItem_MarshalJSON() {
|
||||||
|
item := NewItem(Active, "Active")
|
||||||
|
data, _ := item.MarshalJSON()
|
||||||
|
fmt.Println(string(data))
|
||||||
|
|
||||||
|
var unmarshaledItem Item[Status]
|
||||||
|
_ = unmarshaledItem.UnmarshalJSON(data)
|
||||||
|
fmt.Println(unmarshaledItem.Name(), unmarshaledItem.Value())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// {"name":"Active","value":1}
|
||||||
|
// Active 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry_Add() {
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
if item, found := registry.GetByValue(Active); found {
|
||||||
|
fmt.Println("Found by value:", item.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if item, found := registry.GetByName("Inactive"); found {
|
||||||
|
fmt.Println("Found by name:", item.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Found by value: Active
|
||||||
|
// Found by name: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry_Remove() {
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
|
||||||
|
registry.Add(item1)
|
||||||
|
fmt.Println("Size before removal:", registry.Size())
|
||||||
|
|
||||||
|
removed := registry.Remove(Active)
|
||||||
|
fmt.Println("Removed:", removed)
|
||||||
|
fmt.Println("Size after removal:", registry.Size())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Size before removal: 1
|
||||||
|
// Removed: true
|
||||||
|
// Size after removal: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry_Update() {
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
|
||||||
|
registry.Add(item1)
|
||||||
|
updated := registry.Update(Active, "Activated")
|
||||||
|
fmt.Println("Updated:", updated)
|
||||||
|
|
||||||
|
if item, found := registry.GetByValue(Active); found {
|
||||||
|
fmt.Println("New name:", item.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Updated: true
|
||||||
|
// New name: Activated
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry_Items() {
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
for _, item := range registry.Items() {
|
||||||
|
fmt.Println(item.Name(), item.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Active 1
|
||||||
|
// Inactive 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry_Contains() {
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
registry.Add(item1)
|
||||||
|
|
||||||
|
fmt.Println("Contains Active:", registry.Contains(Active))
|
||||||
|
fmt.Println("Contains Inactive:", registry.Contains(Inactive))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry_Validate() {
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
fmt.Println("Validate Active:", registry.Validate(Active))
|
||||||
|
fmt.Println("Validate Inactive:", registry.Validate(Inactive))
|
||||||
|
fmt.Println("Validate Unknown:", registry.Validate(Unknown))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Validate Active: <nil>
|
||||||
|
// Validate Inactive: <nil>
|
||||||
|
// Validate Unknown: invalid enum value: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry_ValidateAll() {
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
fmt.Println("ValidateAll Active, Inactive:", registry.ValidateAll(Active, Inactive))
|
||||||
|
fmt.Println("ValidateAll Active, Unknown:", registry.ValidateAll(Active, Unknown))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// ValidateAll Active, Inactive: <nil>
|
||||||
|
// ValidateAll Active, Unknown: invalid enum value: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry_Size() {
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
fmt.Println("Initial size:", registry.Size())
|
||||||
|
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
fmt.Println("Size after adding items:", registry.Size())
|
||||||
|
|
||||||
|
registry.Remove(Active)
|
||||||
|
fmt.Println("Size after removing an item:", registry.Size())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Initial size: 0
|
||||||
|
// Size after adding items: 2
|
||||||
|
// Size after removing an item: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry_Range() {
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
registry.Range(func(item *Item[Status]) bool {
|
||||||
|
fmt.Println(item.Name(), item.Value())
|
||||||
|
return true // continue iteration
|
||||||
|
})
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Active 1
|
||||||
|
// Inactive 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry_SortedItems() {
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Inactive, "Inactive")
|
||||||
|
item2 := NewItem(Active, "Active")
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
for _, item := range registry.SortedItems(func(i1, i2 *Item[Status]) bool {
|
||||||
|
return i1.value < i2.value
|
||||||
|
}) {
|
||||||
|
fmt.Println(item.Name(), item.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Active 1
|
||||||
|
// Inactive 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRegistry_Filter() {
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
activeItems := registry.Filter(func(item *Item[Status]) bool {
|
||||||
|
return item.Value() == Active
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, item := range activeItems {
|
||||||
|
fmt.Println(item.Name(), item.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Active 1
|
||||||
|
}
|
||||||
218
enum/enum_test.go
Normal file
218
enum/enum_test.go
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
// Copyright 2025 dudaodong@gmail.com. All rights resulterved.
|
||||||
|
// Use of this source code is governed by MIT license
|
||||||
|
|
||||||
|
package enum
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/duke-git/lancet/v2/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Status int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Unknown Status = iota
|
||||||
|
Active
|
||||||
|
Inactive
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewItem(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestNewItem")
|
||||||
|
|
||||||
|
items := NewItems(
|
||||||
|
Pair[Status]{Value: Active, Name: "Active"},
|
||||||
|
Pair[Status]{Value: Inactive, Name: "Inactive"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(2, len(items))
|
||||||
|
assert.Equal(Active, items[0].Value())
|
||||||
|
assert.Equal("Active", items[0].Name())
|
||||||
|
assert.Equal(Inactive, items[1].Value())
|
||||||
|
assert.Equal("Inactive", items[1].Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestItem_Valid(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestItem_Valid")
|
||||||
|
|
||||||
|
item := NewItem(Active, "Active")
|
||||||
|
assert.Equal(true, item.Valid())
|
||||||
|
|
||||||
|
invalidItem := NewItem(Unknown, "")
|
||||||
|
assert.Equal(false, invalidItem.Valid())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestItem_MarshalJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestItem_MarshalJSON")
|
||||||
|
|
||||||
|
item := NewItem(Active, "Active")
|
||||||
|
data, err := item.MarshalJSON()
|
||||||
|
assert.IsNil(err)
|
||||||
|
assert.Equal("{\"name\":\"Active\",\"value\":1}", string(data))
|
||||||
|
|
||||||
|
var unmarshaledItem Item[Status]
|
||||||
|
err = unmarshaledItem.UnmarshalJSON(data)
|
||||||
|
assert.IsNil(err)
|
||||||
|
assert.Equal(item.Value(), unmarshaledItem.Value())
|
||||||
|
assert.Equal(item.Name(), unmarshaledItem.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistry_AddAndGet(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestRegistry_AddAndGet")
|
||||||
|
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
assert.Equal(2, registry.Size())
|
||||||
|
|
||||||
|
item, ok := registry.GetByValue(Active)
|
||||||
|
assert.Equal(true, ok)
|
||||||
|
assert.Equal("Active", item.Name())
|
||||||
|
|
||||||
|
item, ok = registry.GetByName("Inactive")
|
||||||
|
assert.Equal(true, ok)
|
||||||
|
assert.Equal(Inactive, item.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistry_Remove(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestRegistry_Remove")
|
||||||
|
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
assert.Equal(2, registry.Size())
|
||||||
|
|
||||||
|
removed := registry.Remove(Active)
|
||||||
|
assert.Equal(true, removed)
|
||||||
|
assert.Equal(1, registry.Size())
|
||||||
|
|
||||||
|
_, ok := registry.GetByValue(Active)
|
||||||
|
assert.Equal(false, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistry_Update(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestRegistry_Update")
|
||||||
|
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
registry.Add(item1)
|
||||||
|
|
||||||
|
updated := registry.Update(Active, "Activated")
|
||||||
|
assert.Equal(true, updated)
|
||||||
|
|
||||||
|
item, ok := registry.GetByValue(Active)
|
||||||
|
assert.Equal(true, ok)
|
||||||
|
assert.Equal("Activated", item.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistry_Contains(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestRegistry_Contains")
|
||||||
|
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
registry.Add(item1)
|
||||||
|
|
||||||
|
assert.Equal(true, registry.Contains(Active))
|
||||||
|
assert.Equal(false, registry.Contains(Inactive))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistry_Validate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestRegistry_Validate")
|
||||||
|
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
err := registry.Validate(Active)
|
||||||
|
assert.IsNil(err)
|
||||||
|
err = registry.Validate(Inactive)
|
||||||
|
assert.IsNil(err)
|
||||||
|
|
||||||
|
err = registry.Validate(Unknown)
|
||||||
|
assert.IsNotNil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistry_ValidateAll(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestRegistry_ValidateAll")
|
||||||
|
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
err := registry.ValidateAll(Active, Inactive)
|
||||||
|
assert.IsNil(err)
|
||||||
|
|
||||||
|
err = registry.ValidateAll(Active, Unknown)
|
||||||
|
assert.IsNotNil(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistry_Range(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestRegistry_Range")
|
||||||
|
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
var values []Status
|
||||||
|
registry.Range(func(item *Item[Status]) bool {
|
||||||
|
values = append(values, item.Value())
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(2, len(values))
|
||||||
|
assert.Equal(Active, values[0])
|
||||||
|
assert.Equal(Inactive, values[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistry_SortedItems(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestRegistry_SortedItems")
|
||||||
|
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Inactive, "Inactive")
|
||||||
|
item2 := NewItem(Active, "Active")
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
sortedItems := registry.SortedItems(func(i1, i2 *Item[Status]) bool {
|
||||||
|
return i1.value < i2.value
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(2, len(sortedItems))
|
||||||
|
assert.Equal(Active, sortedItems[0].Value())
|
||||||
|
assert.Equal(Inactive, sortedItems[1].Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistry_Filter(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := internal.NewAssert(t, "TestRegistry_Filter")
|
||||||
|
|
||||||
|
registry := NewRegistry[Status]()
|
||||||
|
item1 := NewItem(Active, "Active")
|
||||||
|
item2 := NewItem(Inactive, "Inactive")
|
||||||
|
registry.Add(item1, item2)
|
||||||
|
|
||||||
|
filteredItems := registry.Filter(func(item *Item[Status]) bool {
|
||||||
|
return item.Value() == Active
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(1, len(filteredItems))
|
||||||
|
assert.Equal(Active, filteredItems[0].Value())
|
||||||
|
}
|
||||||
@@ -19,30 +19,48 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
alphaMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
|
alphaMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
|
||||||
letterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
|
letterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
|
||||||
alphaNumericMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
|
alphaNumericMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
|
||||||
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
|
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
|
||||||
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
|
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
|
||||||
urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
|
urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
|
||||||
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
|
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
|
||||||
emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
|
// emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
|
||||||
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
|
emailMatcher *regexp.Regexp = regexp.MustCompile(`\w+(-+.\w+)*@\w+(-.\w+)*.\w+(-.\w+)*`)
|
||||||
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`^(\d{17})([0-9]|X|x)$`)
|
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
|
||||||
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
|
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])`)
|
||||||
chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`)
|
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
|
||||||
creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`)
|
chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`)
|
||||||
base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`)
|
creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`)
|
||||||
base64URLMatcher *regexp.Regexp = regexp.MustCompile(`^([A-Za-z0-9_-]{4})*([A-Za-z0-9_-]{2}(==)?|[A-Za-z0-9_-]{3}=?)?$`)
|
base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`)
|
||||||
binMatcher *regexp.Regexp = regexp.MustCompile(`^(0b)?[01]+$`)
|
base64URLMatcher *regexp.Regexp = regexp.MustCompile(`^([A-Za-z0-9_-]{4})*([A-Za-z0-9_-]{2}(==)?|[A-Za-z0-9_-]{3}=?)?$`)
|
||||||
hexMatcher *regexp.Regexp = regexp.MustCompile(`^(#|0x|0X)?[0-9a-fA-F]+$`)
|
binMatcher *regexp.Regexp = regexp.MustCompile(`^(0b)?[01]+$`)
|
||||||
visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`)
|
hexMatcher *regexp.Regexp = regexp.MustCompile(`^(#|0x|0X)?[0-9a-fA-F]+$`)
|
||||||
masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`)
|
visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`)
|
||||||
americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`)
|
masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`)
|
||||||
unionPay *regexp.Regexp = regexp.MustCompile("^62[0-5]\\d{13,16}$")
|
americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`)
|
||||||
chinaUnionPay *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`)
|
unionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-5]\\d{13,16}$`)
|
||||||
|
chinaUnionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`)
|
||||||
|
chineseHMPassportMatcher *regexp.Regexp = regexp.MustCompile(`^[CM]\d{8}$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var passportMatcher = map[string]*regexp.Regexp{
|
||||||
|
"CN": regexp.MustCompile(`^P\d{9}$`),
|
||||||
|
"US": regexp.MustCompile(`^\d{9}$`),
|
||||||
|
"GB": regexp.MustCompile(`^[A-Z0-9]{9}$`),
|
||||||
|
"RU": regexp.MustCompile(`^[A-Z]{2}\d{7}$`),
|
||||||
|
"DE": regexp.MustCompile(`^\d{9}$`),
|
||||||
|
"FR": regexp.MustCompile(`^[A-Z]{2}\d{7}$`),
|
||||||
|
"JP": regexp.MustCompile(`^\d{8}$`),
|
||||||
|
"IT": regexp.MustCompile(`^\d{8}$`),
|
||||||
|
"AU": regexp.MustCompile(`^[A-Z]{1}\d{8}$`),
|
||||||
|
"BR": regexp.MustCompile(`^\d{9}$`),
|
||||||
|
"IN": regexp.MustCompile(`^[A-Z]{1,2}\d{7}$`),
|
||||||
|
"HK": regexp.MustCompile(`^M\d{8}$`),
|
||||||
|
"MO": regexp.MustCompile(`^[A-Z]\d{8}$`),
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Identity card formula
|
// Identity card formula
|
||||||
factor = [17]int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
|
factor = [17]int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
|
||||||
@@ -258,21 +276,45 @@ func IsPort(str string) bool {
|
|||||||
// IsUrl check if the string is url.
|
// IsUrl check if the string is url.
|
||||||
// Play: https://go.dev/play/p/pbJGa7F98Ka
|
// Play: https://go.dev/play/p/pbJGa7F98Ka
|
||||||
func IsUrl(str string) bool {
|
func IsUrl(str string) bool {
|
||||||
if str == "" || len(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") {
|
if str == "" {
|
||||||
return false
|
|
||||||
}
|
|
||||||
u, err := url.Parse(str)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(u.Host, ".") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlMatcher.MatchString(str)
|
u, err := url.Parse(str)
|
||||||
|
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
allowedSchemes := map[string]struct{}{
|
||||||
|
"http": {},
|
||||||
|
"https": {},
|
||||||
|
"ftp": {},
|
||||||
|
"ws": {},
|
||||||
|
"wss": {},
|
||||||
|
"file": {},
|
||||||
|
"mailto": {},
|
||||||
|
"data": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := allowedSchemes[u.Scheme]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == "file" || u.Scheme == "mailto" || u.Scheme == "data" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
host := u.Hostname()
|
||||||
|
if !strings.Contains(host, ".") || strings.HasSuffix(host, ".") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// domainRegexp := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9\-\.]*[a-zA-Z0-9]$`)
|
||||||
|
if !dnsMatcher.MatchString(host) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDns check if the string is dns.
|
// IsDns check if the string is dns.
|
||||||
@@ -286,8 +328,6 @@ func IsDns(dns string) bool {
|
|||||||
func IsEmail(email string) bool {
|
func IsEmail(email string) bool {
|
||||||
_, err := mail.ParseAddress(email)
|
_, err := mail.ParseAddress(email)
|
||||||
return err == nil
|
return err == nil
|
||||||
|
|
||||||
// return emailMatcher.MatchString(email)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsChineseMobile check if the string is chinese mobile number.
|
// IsChineseMobile check if the string is chinese mobile number.
|
||||||
@@ -460,12 +500,13 @@ func IsZeroValue(value any) bool {
|
|||||||
func IsGBK(data []byte) bool {
|
func IsGBK(data []byte) bool {
|
||||||
i := 0
|
i := 0
|
||||||
for i < len(data) {
|
for i < len(data) {
|
||||||
if data[i] <= 0xff {
|
if data[i] < 0x81 {
|
||||||
i++
|
i++
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
if data[i] >= 0x81 &&
|
if data[i] >= 0x81 &&
|
||||||
data[i] <= 0xfe &&
|
data[i] <= 0xfe &&
|
||||||
|
i+1 < len(data) &&
|
||||||
data[i+1] >= 0x40 &&
|
data[i+1] >= 0x40 &&
|
||||||
data[i+1] <= 0xfe &&
|
data[i+1] <= 0xfe &&
|
||||||
data[i+1] != 0xf7 {
|
data[i+1] != 0xf7 {
|
||||||
@@ -561,12 +602,71 @@ func IsAmericanExpress(v string) bool {
|
|||||||
|
|
||||||
// IsUnionPay check if a give string is a valid union pay nubmer or not.
|
// IsUnionPay check if a give string is a valid union pay nubmer or not.
|
||||||
// Play: https://go.dev/play/p/CUHPEwEITDf
|
// Play: https://go.dev/play/p/CUHPEwEITDf
|
||||||
func IsUnionPay(v string) bool {
|
func IsUnionPay(cardNo string) bool {
|
||||||
return unionPay.MatchString(v)
|
if len(cardNo) < 16 || len(cardNo) > 19 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
matched, _ := regexp.MatchString(`^\d+$`, cardNo)
|
||||||
|
if !matched {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cardNo) < 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := cardNo[:3]
|
||||||
|
prefixNum, err := strconv.Atoi(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefixNum < 620 || prefixNum > 625 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsChinaUnionPay check if a give string is a valid china union pay nubmer or not.
|
// IsChinaUnionPay check if a give string is a valid china union pay nubmer or not.
|
||||||
// Play: https://go.dev/play/p/yafpdxLiymu
|
// Play: https://go.dev/play/p/yafpdxLiymu
|
||||||
func IsChinaUnionPay(v string) bool {
|
func IsChinaUnionPay(cardNo string) bool {
|
||||||
return chinaUnionPay.MatchString(v)
|
return chinaUnionPayMatcher.MatchString(cardNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// luhnCheck checks if the credit card number is valid using the Luhn algorithm.
|
||||||
|
func luhnCheck(card string) bool {
|
||||||
|
var sum int
|
||||||
|
alt := false
|
||||||
|
for i := len(card) - 1; i >= 0; i-- {
|
||||||
|
n := int(card[i] - '0')
|
||||||
|
if alt {
|
||||||
|
n *= 2
|
||||||
|
if n > 9 {
|
||||||
|
n -= 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sum += n
|
||||||
|
alt = !alt
|
||||||
|
}
|
||||||
|
return sum%10 == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPassport checks if the passport number is valid for a given country.
|
||||||
|
// country is a two-letter country code (ISO 3166-1 alpha-2).
|
||||||
|
// Play: todo
|
||||||
|
func IsPassport(passport, country string) bool {
|
||||||
|
if matcher, ok := passportMatcher[country]; ok {
|
||||||
|
return matcher.MatchString(passport)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsChineseHMPassport checks if the string is a valid Chinese Hong Kong and Macau Travel Permit number.
|
||||||
|
// Chinese Hong Kong and Macau Travel Permit format: C or M + 8 digits (e.g., C12345678, M12345678).
|
||||||
|
// Play: https://go.dev/play/p/TODO
|
||||||
|
func IsChineseHMPassport(hmPassport string) bool {
|
||||||
|
return chineseHMPassportMatcher.MatchString(hmPassport)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ func ExampleIsUrl() {
|
|||||||
fmt.Println(result3)
|
fmt.Println(result3)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// true
|
// false
|
||||||
// true
|
// true
|
||||||
// false
|
// false
|
||||||
}
|
}
|
||||||
@@ -683,3 +683,42 @@ func ExampleIsAlphaNumeric() {
|
|||||||
// true
|
// true
|
||||||
// false
|
// false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleIsPassport() {
|
||||||
|
result1 := IsPassport("P123456789", "CN")
|
||||||
|
result2 := IsPassport("123456789", "US")
|
||||||
|
result3 := IsPassport("AB1234567", "RU")
|
||||||
|
result4 := IsPassport("123456789", "CN")
|
||||||
|
|
||||||
|
fmt.Println(result1)
|
||||||
|
fmt.Println(result2)
|
||||||
|
fmt.Println(result3)
|
||||||
|
fmt.Println(result4)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// true
|
||||||
|
// true
|
||||||
|
// true
|
||||||
|
// false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleIsChineseHMPassport() {
|
||||||
|
result1 := IsChineseHMPassport("C12345678")
|
||||||
|
result2 := IsChineseHMPassport("C00000000")
|
||||||
|
result3 := IsChineseHMPassport("M12345678")
|
||||||
|
result4 := IsChineseHMPassport("c12345678")
|
||||||
|
result5 := IsChineseHMPassport("C1234567")
|
||||||
|
|
||||||
|
fmt.Println(result1)
|
||||||
|
fmt.Println(result2)
|
||||||
|
fmt.Println(result3)
|
||||||
|
fmt.Println(result4)
|
||||||
|
fmt.Println(result5)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// true
|
||||||
|
// true
|
||||||
|
// true
|
||||||
|
// false
|
||||||
|
// false
|
||||||
|
}
|
||||||
|
|||||||
@@ -437,10 +437,32 @@ func TestIsUrl(t *testing.T) {
|
|||||||
|
|
||||||
assert := internal.NewAssert(t, "TestIsUrl")
|
assert := internal.NewAssert(t, "TestIsUrl")
|
||||||
|
|
||||||
assert.Equal(true, IsUrl("http://abc.com"))
|
tests := []struct {
|
||||||
assert.Equal(true, IsUrl("abc.com"))
|
input string
|
||||||
assert.Equal(true, IsUrl("a.b.com"))
|
expected bool
|
||||||
assert.Equal(false, IsUrl("abc"))
|
}{
|
||||||
|
{"http://abc.com", true},
|
||||||
|
{"https://abc.com", true},
|
||||||
|
{"ftp://abc.com", true},
|
||||||
|
{"http://abc.com/path?query=123", true},
|
||||||
|
{"https://abc.com/path/to/resource", true},
|
||||||
|
{"ws://abc.com", true},
|
||||||
|
{"wss://abc.com", true},
|
||||||
|
{"mailto://abc.com", true},
|
||||||
|
{"file://path/to/file", true},
|
||||||
|
{"data://text/plain;base64,SGVsbG8sIFdvcmxkIQ==", true},
|
||||||
|
{"http://abc.com/path/to/resource?query=123#fragment", true},
|
||||||
|
|
||||||
|
{"abc", false},
|
||||||
|
{"http://", false},
|
||||||
|
{"http://abc", false},
|
||||||
|
{"http://abc:8080", false},
|
||||||
|
{"http://abc:99999999", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
assert.Equal(tt.expected, IsUrl(tt.input))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsDns(t *testing.T) {
|
func TestIsDns(t *testing.T) {
|
||||||
@@ -477,12 +499,24 @@ func TestIsEmail(t *testing.T) {
|
|||||||
|
|
||||||
func TestContainChinese(t *testing.T) {
|
func TestContainChinese(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
assert := internal.NewAssert(t, "TestContainChinese")
|
assert := internal.NewAssert(t, "TestContainChinese")
|
||||||
|
|
||||||
assert.Equal(true, ContainChinese("你好"))
|
tests := []struct {
|
||||||
assert.Equal(true, ContainChinese("你好hello"))
|
input string
|
||||||
assert.Equal(false, ContainChinese("hello"))
|
expected bool
|
||||||
|
}{
|
||||||
|
{"你好", true},
|
||||||
|
{"hello", false},
|
||||||
|
{"你好hello", true},
|
||||||
|
{"hello你好", true},
|
||||||
|
{"", false},
|
||||||
|
{"123", false},
|
||||||
|
{"!@#$%^&*()", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
assert.Equal(tt.expected, ContainChinese(tt.input))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsChineseMobile(t *testing.T) {
|
func TestIsChineseMobile(t *testing.T) {
|
||||||
@@ -490,8 +524,20 @@ func TestIsChineseMobile(t *testing.T) {
|
|||||||
|
|
||||||
assert := internal.NewAssert(t, "TestIsChineseMobile")
|
assert := internal.NewAssert(t, "TestIsChineseMobile")
|
||||||
|
|
||||||
assert.Equal(true, IsChineseMobile("13263527980"))
|
tests := []struct {
|
||||||
assert.Equal(false, IsChineseMobile("434324324"))
|
input string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"13263527980", true},
|
||||||
|
{"1326352798", false},
|
||||||
|
{"132635279801", false},
|
||||||
|
{"1326352798a", false},
|
||||||
|
{"1326352798@", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
assert.Equal(tt.expected, IsChineseMobile(tt.input))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsChinesePhone(t *testing.T) {
|
func TestIsChinesePhone(t *testing.T) {
|
||||||
@@ -887,7 +933,7 @@ func TestIsUnionPay(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
assert := internal.NewAssert(t, "TestIsUnionPay")
|
assert := internal.NewAssert(t, "TestIsUnionPay")
|
||||||
|
|
||||||
assert.Equal(true, IsUnionPay("6221263430109903"))
|
assert.Equal(true, IsUnionPay("6228480402564890"))
|
||||||
assert.Equal(false, IsUnionPay("3782822463100007"))
|
assert.Equal(false, IsUnionPay("3782822463100007"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -895,8 +941,25 @@ func TestIsChinaUnionPay(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
assert := internal.NewAssert(t, "TestIsChinaUnionPay")
|
assert := internal.NewAssert(t, "TestIsChinaUnionPay")
|
||||||
|
|
||||||
assert.Equal(true, IsChinaUnionPay("6250941006528599"))
|
tests := []struct {
|
||||||
assert.Equal(false, IsChinaUnionPay("3782822463100007"))
|
cardNumber string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"6228480420000000000", true},
|
||||||
|
{"6214830000000000", true},
|
||||||
|
{"6230580000000000000", true},
|
||||||
|
{"6259640000000000000", true},
|
||||||
|
{"6260000000000000000", true},
|
||||||
|
{"6288888888888888", true},
|
||||||
|
|
||||||
|
// 非银联前缀
|
||||||
|
{"4123456789012345", false},
|
||||||
|
{"3528000000000000", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
assert.Equal(tt.expected, IsChinaUnionPay(tt.cardNumber))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsAlphaNumeric(t *testing.T) {
|
func TestIsAlphaNumeric(t *testing.T) {
|
||||||
@@ -924,3 +987,72 @@ func TestIsAlphaNumeric(t *testing.T) {
|
|||||||
assert.Equal(tt.expected, IsAlphaNumeric(tt.input))
|
assert.Equal(tt.expected, IsAlphaNumeric(tt.input))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsPassport(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
assert := internal.NewAssert(t, "TestIsPassport")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
passport string
|
||||||
|
countryCode string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"P123456789", "CN", true},
|
||||||
|
{"123456789", "US", true},
|
||||||
|
{"A12345678", "GB", true},
|
||||||
|
{"AB1234567", "FR", true},
|
||||||
|
{"12345678", "JP", true},
|
||||||
|
{"M12345678", "HK", true},
|
||||||
|
{"A12345678", "MO", true},
|
||||||
|
{"A1234567", "IN", true},
|
||||||
|
{"12345678", "IT", true},
|
||||||
|
{"A12345678", "AU", true},
|
||||||
|
{"123456789", "BR", true},
|
||||||
|
{"AB1234567", "RU", true},
|
||||||
|
{"123456789", "CN", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
assert.Equal(tt.expected, IsPassport(tt.passport, tt.countryCode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsChineseHMPassport(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
assert := internal.NewAssert(t, "TestIsChineseHMPassport")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"C12345678", true},
|
||||||
|
{"C00000000", true},
|
||||||
|
{"C99999999", true},
|
||||||
|
{"M12345678", true}, // M prefix
|
||||||
|
{"M00000000", true}, // M prefix
|
||||||
|
{"M99999999", true}, // M prefix
|
||||||
|
{"c12345678", false}, // lowercase c
|
||||||
|
{"m12345678", false}, // lowercase m
|
||||||
|
{"C1234567", false}, // 7 digits
|
||||||
|
{"M1234567", false}, // 7 digits with M
|
||||||
|
{"C123456789", false}, // 9 digits
|
||||||
|
{"M123456789", false}, // 9 digits with M
|
||||||
|
{"C1234567a", false}, // contains letter
|
||||||
|
{"M1234567a", false}, // contains letter with M
|
||||||
|
{"D12345678", false}, // starts with D
|
||||||
|
{"12345678", false}, // no prefix
|
||||||
|
{"CC12345678", false}, // double C
|
||||||
|
{"MM12345678", false}, // double M
|
||||||
|
{"C 12345678", false}, // contains space
|
||||||
|
{"M 12345678", false}, // contains space with M
|
||||||
|
{"C12345-678", false}, // contains dash
|
||||||
|
{"M12345-678", false}, // contains dash with M
|
||||||
|
{"", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
assert.Equal(tt.expected, IsChineseHMPassport(tt.input))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user