1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-03-01 00:35:28 +08:00

Compare commits

...

15 Commits

Author SHA1 Message Date
dudaodong
8bbae69175 feat: add try-catch-finally implementation 2024-11-04 14:46:25 +08:00
dudaodong
840ea8f3c5 fix: fix code in UnZip demo 2024-10-25 11:14:15 +08:00
dudaodong
a4e89bd7c1 feat: add ConcatBy 2024-10-24 15:38:12 +08:00
dudaodong
2015d36b08 feat: add JoinFunc 2024-10-24 14:56:13 +08:00
dudaodong
921f218ef7 test: run test cases on rc branch 2024-10-18 16:40:20 +08:00
dudaodong
0a2cc9c928 doc: update doc for RandNumberOfLength and GetExeOrDllVersion 2024-10-18 16:38:39 +08:00
今晚打打
ed93aae970 feat:add GetExeDllVersion for fileutil,random,package (#257)
* 增加GetExeDllVersion函数获取exe,dll版本号

* 多打文字

* 定位忘记...

* 单独剥离出来一个为windows的,增加RandNumLen为取指定长度随机数.

* ....

* 增加test测试
2024-10-18 16:17:14 +08:00
BoWen Qiu
1671f7856a feat(netutil): add async http request for http client (#256)
* fix(algorithm): fix bug of lrucache in issue #251

* feat(netutil): add async send request for HttpClient
2024-10-18 15:14:52 +08:00
燕归来
1008dd4956 fix: fix compile error (#255) (#258) 2024-10-18 15:09:28 +08:00
BoWen Qiu
a254ebdc8e fix(algorithm): fix bug of lrucache in issue #251 (#254) 2024-10-16 09:55:38 +08:00
dudaodong
0bc11001a4 fix: add rsa public and private key example file 2024-10-12 15:16:39 +08:00
dudaodong
85d98ad915 Merge branch 'rc' into v2 2024-10-12 14:06:15 +08:00
77
cb08613ac3 fix(orderedmap): fix set bug (#252)
set : when the key is present, the value of the data map should be set,
not the value of the list

Co-authored-by: congziqi <congziqi@lixiang.com>
2024-10-12 14:05:13 +08:00
dudaodong
bad1b05224 doc: add doc for RsaSign and RsaVerifySign 2024-10-11 15:38:42 +08:00
dudaodong
527328739a feat: add RsaSign and RsaVerifySign 2024-10-10 15:40:32 +08:00
36 changed files with 1569 additions and 71 deletions

2
.gitignore vendored
View File

@@ -8,7 +8,7 @@ fileutil/*.link
fileutil/unzip/*
fileutil/tempdir/*
slice/testdata/*
cryptor/*.pem
# cryptor/*.pem
test
docs/node_modules
docs/.vitepress/cache

View File

@@ -44,7 +44,7 @@ func (l *LRUCache[K, V]) Get(key K) (V, bool) {
node, ok := l.cache[key]
if ok {
l.moveToHead(node)
l.moveToTail(node)
return node.value, true
}
@@ -66,7 +66,7 @@ func (l *LRUCache[K, V]) Put(key K, value V) {
}
} else {
node.value = value
l.moveToHead(node)
l.moveToTail(node)
}
l.length = len(l.cache)
}
@@ -79,7 +79,7 @@ func (l *LRUCache[K, V]) Delete(key K) bool {
delete(l.cache, key)
return true
}
l.length = len(l.cache)
return false
}
@@ -112,7 +112,7 @@ func (l *LRUCache[K, V]) deleteNode(node *lruNode[K, V]) K {
return node.key
}
func (l *LRUCache[K, V]) moveToHead(node *lruNode[K, V]) {
func (l *LRUCache[K, V]) moveToTail(node *lruNode[K, V]) {
if l.tail == node {
return
}

View File

@@ -8,16 +8,20 @@ package cryptor
import (
"bytes"
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"errors"
"io"
"os"
"strings"
)
// AesEcbEncrypt encrypt data with key use AES ECB algorithm
@@ -591,6 +595,7 @@ func RsaEncrypt(data []byte, pubKeyFileName string) []byte {
if err != nil {
panic(err)
}
return cipherText
}
@@ -624,6 +629,7 @@ func RsaDecrypt(data []byte, privateKeyFileName string) []byte {
if err != nil {
panic(err)
}
return plainText
}
@@ -655,3 +661,150 @@ func RsaDecryptOAEP(ciphertext []byte, label []byte, key rsa.PrivateKey) ([]byte
return decryptedBytes, nil
}
// RsaSign signs the data with RSA.
// Play: todo
func RsaSign(hash crypto.Hash, data []byte, privateKeyFileName string) ([]byte, error) {
privateKey, err := loadRasPrivateKey(privateKeyFileName)
if err != nil {
return nil, err
}
hashed, err := hashData(hash, data)
if err != nil {
return nil, err
}
return rsa.SignPKCS1v15(rand.Reader, privateKey, hash, hashed)
}
// RsaVerifySign verifies the signature of the data with RSA.
// Play: todo
func RsaVerifySign(hash crypto.Hash, data, signature []byte, pubKeyFileName string) error {
publicKey, err := loadRsaPublicKey(pubKeyFileName)
if err != nil {
return err
}
hashed, err := hashData(hash, data)
if err != nil {
return err
}
return rsa.VerifyPKCS1v15(publicKey, hash, hashed, signature)
}
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
func loadRsaPublicKey(filename string) (*rsa.PublicKey, error) {
pubKeyData, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
block, _ := pem.Decode(pubKeyData)
if block == nil {
return nil, errors.New("failed to decode PEM block containing the public key")
}
var pubKey *rsa.PublicKey
blockType := strings.ToUpper(block.Type)
if blockType == "RSA PUBLIC KEY" {
pubKey, err = x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
// todo: here should be a bug, should return nil, err
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
var ok bool
pubKey, ok = key.(*rsa.PublicKey)
if !ok {
return nil, errors.New("failed to parse RSA private key")
}
}
} else if blockType == "PUBLIC KEY" {
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
var ok bool
pubKey, ok = key.(*rsa.PublicKey)
if !ok {
return nil, errors.New("failed to parse RSA private key")
}
} else {
return nil, errors.New("unsupported key type")
}
return pubKey, nil
}
// loadRsaPrivateKey loads and parses a PEM encoded private key file.
func loadRasPrivateKey(filename string) (*rsa.PrivateKey, error) {
priKeyData, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
block, _ := pem.Decode(priKeyData)
if block == nil {
return nil, errors.New("failed to decode PEM block containing the private key")
}
var privateKey *rsa.PrivateKey
blockType := strings.ToUpper(block.Type)
// PKCS#1 format
if blockType == "RSA PRIVATE KEY" {
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
} else if blockType == "PRIVATE KEY" { // PKCS#8 format
priKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
var ok bool
privateKey, ok = priKey.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("failed to parse RSA private key")
}
} else {
return nil, errors.New("unsupported key type")
}
return privateKey, nil
}
// hashData returns the hash value of the data, using the specified hash function
func hashData(hash crypto.Hash, data []byte) ([]byte, error) {
if !hash.Available() {
return nil, errors.New("unsupported hash algorithm")
}
var hashed []byte
switch hash {
case crypto.SHA224:
h := sha256.Sum224(data)
hashed = h[:]
case crypto.SHA256:
h := sha256.Sum256(data)
hashed = h[:]
case crypto.SHA384:
h := sha512.Sum384(data)
hashed = h[:]
case crypto.SHA512:
h := sha512.Sum512(data)
hashed = h[:]
default:
return nil, errors.New("unsupported hash algorithm")
}
return hashed, nil
}

View File

@@ -1,6 +1,7 @@
package cryptor
import (
"crypto"
"fmt"
)
@@ -284,7 +285,7 @@ func ExampleDesOfbDecrypt() {
func ExampleGenerateRsaKey() {
// Create ras private and public pem file
err := GenerateRsaKey(4096, "rsa_private.pem", "rsa_public.pem")
err := GenerateRsaKey(4096, "rsa_private_example.pem", "rsa_public_example.pem")
if err != nil {
return
}
@@ -297,14 +298,14 @@ func ExampleGenerateRsaKey() {
func ExampleRsaEncrypt() {
// Create ras private and public pem file
err := GenerateRsaKey(4096, "rsa_private.pem", "rsa_public.pem")
err := GenerateRsaKey(4096, "rsa_private_example.pem", "rsa_public_example.pem")
if err != nil {
return
}
data := []byte("hello")
encrypted := RsaEncrypt(data, "rsa_public.pem")
decrypted := RsaDecrypt(encrypted, "rsa_private.pem")
encrypted := RsaEncrypt(data, "rsa_public_example.pem")
decrypted := RsaDecrypt(encrypted, "rsa_private_example.pem")
fmt.Println(string(decrypted))
@@ -314,14 +315,14 @@ func ExampleRsaEncrypt() {
func ExampleRsaDecrypt() {
// Create ras private and public pem file
err := GenerateRsaKey(4096, "rsa_private.pem", "rsa_public.pem")
err := GenerateRsaKey(4096, "rsa_private_example.pem", "rsa_public_example.pem")
if err != nil {
return
}
data := []byte("hello")
encrypted := RsaEncrypt(data, "rsa_public.pem")
decrypted := RsaDecrypt(encrypted, "rsa_private.pem")
encrypted := RsaEncrypt(data, "rsa_public_example.pem")
decrypted := RsaDecrypt(encrypted, "rsa_private_example.pem")
fmt.Println(string(decrypted))
@@ -536,3 +537,49 @@ func ExampleRsaEncryptOAEP() {
// Output:
// hello world
}
func ExampleRsaSign() {
data := []byte("This is a test data for RSA signing")
hash := crypto.SHA256
privateKey := "./rsa_private_example.pem"
publicKey := "./rsa_public_example.pem"
signature, err := RsaSign(hash, data, privateKey)
if err != nil {
return
}
err = RsaVerifySign(hash, data, signature, publicKey)
if err != nil {
return
}
fmt.Println("ok")
// Output:
// ok
}
func ExampleRsaVerifySign() {
data := []byte("This is a test data for RSA signing")
hash := crypto.SHA256
privateKey := "./rsa_private_example.pem"
publicKey := "./rsa_public_example.pem"
signature, err := RsaSign(hash, data, privateKey)
if err != nil {
return
}
err = RsaVerifySign(hash, data, signature, publicKey)
if err != nil {
return
}
fmt.Println("ok")
// Output:
// ok
}

View File

@@ -1,6 +1,7 @@
package cryptor
import (
"crypto"
"testing"
"github.com/duke-git/lancet/v2/internal"
@@ -139,13 +140,13 @@ func TestDesOfbEncrypt(t *testing.T) {
func TestRsaEncrypt(t *testing.T) {
t.Parallel()
err := GenerateRsaKey(4096, "rsa_private.pem", "rsa_public.pem")
err := GenerateRsaKey(4096, "./rsa_private_example.pem", "./rsa_public_example.pem")
if err != nil {
t.FailNow()
}
data := []byte("hello world")
encrypted := RsaEncrypt(data, "rsa_public.pem")
decrypted := RsaDecrypt(encrypted, "rsa_private.pem")
encrypted := RsaEncrypt(data, "./rsa_public_example.pem")
decrypted := RsaDecrypt(encrypted, "./rsa_private_example.pem")
assert := internal.NewAssert(t, "TestRsaEncrypt")
assert.Equal(string(data), string(decrypted))
@@ -170,7 +171,6 @@ func TestRsaEncryptOAEP(t *testing.T) {
}
func TestAesGcmEncrypt(t *testing.T) {
t.Parallel()
data := "hello world"
@@ -182,3 +182,53 @@ func TestAesGcmEncrypt(t *testing.T) {
assert := internal.NewAssert(t, "TestAesGcmEncrypt")
assert.Equal(data, string(decrypted))
}
func TestRsaSignAndVerify(t *testing.T) {
t.Parallel()
data := []byte("This is a test data for RSA signing")
hash := crypto.SHA256
t.Run("RSA Sign and Verify", func(t *testing.T) {
privateKey := "./rsa_private_example.pem"
publicKey := "./rsa_public_example.pem"
signature, err := RsaSign(hash, data, privateKey)
if err != nil {
t.Fatalf("RsaSign failed: %v", err)
}
err = RsaVerifySign(hash, data, signature, publicKey)
if err != nil {
t.Fatalf("RsaVerifySign failed: %v", err)
}
})
t.Run("RSA Sign and Verify Invalid Signature", func(t *testing.T) {
publicKey := "./rsa_public_example.pem"
invalidSig := []byte("InvalidSignature")
err := RsaVerifySign(hash, data, invalidSig, publicKey)
if err == nil {
t.Fatalf("RsaVerifySign failed: %v", err)
}
})
t.Run("RSA Sign and Verify With Different Hash", func(t *testing.T) {
publicKey := "./rsa_public_example.pem"
privateKey := "./rsa_private_example.pem"
hashSign := crypto.SHA256
hashVerify := crypto.SHA512
signature, err := RsaSign(hashSign, data, privateKey)
if err != nil {
t.Fatalf("RsaSign failed: %v", err)
}
err = RsaVerifySign(hashVerify, data, signature, publicKey)
if err == nil {
t.Fatalf("RsaVerifySign failed: %v", err)
}
})
}

View File

@@ -0,0 +1,51 @@
-----BEGIN rsa private key-----
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
-----END rsa private key-----

View File

@@ -0,0 +1,14 @@
-----BEGIN rsa public key-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsELqn9abxs7ZK81Ipbp5
+E/5SrOQycAalnRCES3YPOoAV3Ir+99WjEi9Ak4EYS3VrvgISSNiRx1tHDNpBlBq
GF+Mww8wAUmrcXsEndbHma/g3zHGueua9GK0l+hQJ/grkatykgD81gam/SPOMPnJ
SsoQlsTgM47ZHM35adiXqL/gDMZ70lQdB2+03WJXlvfYZWpHsXDqUkveEZbLgZST
36ax8ap+Ge2GoLlD1sNapRxMBH1PGNdzSdV7ZJmzQ3WmjwDAddLX73YE6vhR5tDA
XhrSweNPtIjUhGJsgkW9VTEQWeW9cZmFo/jYuvEDgBnZOh1DJe2BHx06Ymr8Zcgs
OBYddANsk750II8+qUnBekiJkpo0ldCK2VnsLoLM3YCf3Lk7VYuHP6KiCXyh6Fzb
Y1DTRHJRpMJYT8t5xyQis581DbRVPkcse3ig5KsQLe5XXbBBegVrQJpy1b440uZV
W2eSpStX5ZAfNVsU0ZPdI4/i6UNMEa6c7tf83S21Q0K6SEZ5PrfBJECRDxFzJWea
mwppHY8fdeItVk/cxBJ9XRyOyG9pJKMnOMFD++LzGP6uL5FKov7BSplOIMqsJzwh
4zkIjAhgbGVQZm17kg1xvF30c2fgijNEoSIuVWJzlQyQ9cmSaSmmi+SPmf/C/IcU
DF0nikERs1v58vNESvcZP80CAwEAAQ==
-----END rsa public key-----

View File

@@ -70,6 +70,9 @@ import (
- [GenerateRsaKeyPair](#GenerateRsaKeyPair)
- [RsaEncryptOAEP](#RsaEncryptOAEP)
- [RsaDecryptOAEP](#RsaDecryptOAEP)
- [RsaSign](#RsaSign)
- [RsaVerifySign](#RsaVerifySign)
<div STYLE="page-break-after: always;"></div>
@@ -1607,3 +1610,81 @@ func main() {
// hello world
}
```
### <span id="RsaSign">RsaSign</span>
<p>应用RSA算法签名数据。</p>
<b>函数签名:</b>
```go
func RsaSign(hash crypto.Hash, data []byte, privateKeyFileName string) ([]byte, error)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
data := []byte("This is a test data for RSA signing")
hash := crypto.SHA256
privateKey := "./rsa_private.pem"
publicKey := "./rsa_public.pem"
signature, err := RsaSign(hash, data, privateKey)
if err != nil {
return
}
err = RsaVerifySign(hash, data, signature, publicKey)
if err != nil {
return
}
}
```
### <span id="RsaVerifySign">RsaVerifySign</span>
<p>验证数据的签名是否符合RSA算法。</p>
<b>函数签名:</b>
```go
func RsaVerifySign(hash crypto.Hash, data, signature []byte, pubKeyFileName string) error
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
data := []byte("This is a test data for RSA signing")
hash := crypto.SHA256
privateKey := "./rsa_private.pem"
publicKey := "./rsa_public.pem"
signature, err := RsaSign(hash, data, privateKey)
if err != nil {
return
}
err = RsaVerifySign(hash, data, signature, publicKey)
if err != nil {
return
}
}
```

View File

@@ -52,6 +52,7 @@ import (
- [ReadFile](#ReadFile)
- [ChunkRead](#ChunkRead)
- [ParallelChunkRead](#ParallelChunkRead)
- [GetExeOrDllVersion](#GetExeOrDllVersion)
<div STYLE="page-break-after: always;"></div>
@@ -559,7 +560,7 @@ import (
)
func main() {
err := fileutil.Zip("./test.zip", "./unzip/test.txt")
err := fileutil.UnZip("./test.zip", "./test.txt")
if err != nil {
fmt.Println(err)
}
@@ -1076,4 +1077,34 @@ func main() {
// Jim,21,male
// 2
}
```
### <span id="GetExeOrDllVersion">GetExeOrDllVersion</span>
<p>返回exe,dll文件版本号(仅Window平台).</p>
<b>函数签名:</b>
```go
func GetExeOrDllVersion(filePath string) (string, error)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/fileutil"
)
func main() {
v, err := fileutil.GetExeOrDllVersion(`C:\Program Files\Tencent\WeChat\WeChat.exe`)
if err != nil {
panic(err)
}
fmt.Println(v)
// Output:
// 3.9.10.19
}
```

View File

@@ -40,6 +40,7 @@ import (
- [RandStringSlice](#RandStringSlice)
- [RandBool](#RandBool)
- [RandBoolSlice](#RandBoolSlice)
- [RandNumberOfLength](#RandNumberOfLength)
<div STYLE="page-break-after: always;"></div>
@@ -522,4 +523,29 @@ func main() {
result := random.RandBoolSlice(2)
fmt.Println(result) // [true false] (random)
}
```
### <span id="RandNumberOfLength">RandNumberOfLength</span>
<p>生成指定长度的随机数</p>
<b>函数签名:</b>
```go
func RandNumberOfLength(len int) int
```
<b>实例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/random"
)
func main() {
i := random.RandNumberOfLength(2)
fmt.Println(i) // 21 (random number of length 2)
}
```

View File

@@ -106,6 +106,9 @@ import (
- [RightPadding](#RightPadding)
- [LeftPadding](#LeftPadding)
- [Frequency](#Frequency)
- [JoinFunc](#JoinFunc)
- [ConcatBy](#ConcatBy)
<div STYLE="page-break-after: always;"></div>
@@ -2984,4 +2987,81 @@ func main() {
// Output:
// map[a:1 b:2 c:3]
}
```
### <span id="JoinFunc">JoinFunc</span>
<p>将切片元素用给定的分隔符连接成一个单一的字符串。</p>
<b>函数签名:</b>
```go
func JoinFunc[T any](slice []T, sep string, transform func(T) T) string
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result := slice.JoinFunc([]string{"a", "b", "c"}, ", ", func(s string) string {
return strings.ToUpper(s)
})
fmt.Println(result)
// Output:
// A, B, C
}
```
### <span id="ConcatBy">ConcatBy</span>
<p>将切片中的元素连接成一个值,使用指定的分隔符和连接器函数。</p>
<b>函数签名:</b>
```go
func ConcatBy[T any](slice []T, sep T, connector func(T, T) T) T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](todo)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
type Person struct {
Name string
Age int
}
people := []Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
}
sep := Person{Name: " | ", Age: 0}
personConnector := func(a, b Person) Person {
return Person{Name: a.Name + b.Name, Age: a.Age + b.Age}
}
result := slice.ConcatBy(people, sep, personConnector)
fmt.Println(result.Name)
fmt.Println(result.Age)
// Output:
// Alice | Bob | Charlie
// 90
}
```

View File

@@ -35,6 +35,7 @@ import (
- [XError_Info](#XError_Info)
- [XError_Error](#XError_Error)
- [TryUnwrap](#TryUnwrap)
- [TryCatch](#TryCatch)
<div STYLE="page-break-after: always;"></div>
@@ -167,12 +168,12 @@ import (
func main() {
err1 := xerror.New("error").With("level", "high")
err2 := err1.Wrap(errors.New("invalid username"))
err2 := err1.Wrap(errors.New("invalid username"))
fmt.Println(err2.Error())
fmt.Println(err2.Error())
// Output:
// error: invalid username
// Output:
// error: invalid username
}
```
@@ -489,3 +490,56 @@ func main() {
// true
}
```
### <span id="TryCatch">TryCatch</span>
<p>简单实现的java风格异常处理try-catch-finally。try catch不符合go错误处理风格谨慎使用。</p>
<b>函数签名:</b>
```go
func NewTryCatch(ctx context.Context) *TryCatch
func (tc *TryCatch) Try(tryFunc func(ctx context.Context) error) *TryCatch
func (tc *TryCatch) Catch(catchFunc func(ctx context.Context, err error)) *TryCatch
func (tc *TryCatch) Finally(finallyFunc func(ctx context.Context)) *TryCatch
func (tc *TryCatch) Do()
```
<b>示例:<span style="float:right;display:inline-block">[运行](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/xerror"
)
func main() {
calledFinally := false
calledCatch := false
tc := xerror.NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
return errors.New("error in try block")
}).Catch(func(ctx context.Context, err error) {
calledCatch = true
// Error in try block at /path/xxx.go:{line_number} - Cause: error message
// fmt.Println(err.Error())
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
fmt.Println(calledCatch)
fmt.Println(calledFinally)
// Output:
// true
// true
}
```

View File

@@ -70,6 +70,8 @@ import (
- [GenerateRsaKeyPair](#GenerateRsaKeyPair)
- [RsaEncryptOAEP](#RsaEncryptOAEP)
- [RsaDecryptOAEP](#RsaDecryptOAEP)
- [RsaSign](#RsaSign)
- [RsaVerifySign](#RsaVerifySign)
<div STYLE="page-break-after: always;"></div>
@@ -1061,13 +1063,13 @@ import (
func main() {
str := "hello"
key := "12345"
key := "12345"
hms := cryptor.HmacSha512WithBase64(str, key)
fmt.Println(hms)
hms := cryptor.HmacSha512WithBase64(str, key)
fmt.Println(hms)
// Output:
// 3Y8SkKndI9NU4lJtmi6c6M///dN8syCADRxsE9Lvw2Mog3ahlsVFja9T+OGqa0Wm2FYwPVwKIGS/+XhYYdSM/A==
// Output:
// 3Y8SkKndI9NU4lJtmi6c6M///dN8syCADRxsE9Lvw2Mog3ahlsVFja9T+OGqa0Wm2FYwPVwKIGS/+XhYYdSM/A==
}
```
@@ -1124,10 +1126,10 @@ import (
func main() {
md5Str := cryptor.Md5StringWithBase64("hello")
fmt.Println(md5Str)
fmt.Println(md5Str)
// Output:
// XUFAKrxLKna5cZ2REBfFkg==
// Output:
// XUFAKrxLKna5cZ2REBfFkg==
}
```
@@ -1153,10 +1155,10 @@ import (
func main() {
md5Str := cryptor.Md5Byte([]byte{'a'})
fmt.Println(md5Str)
fmt.Println(md5Str)
// Output:
// 0cc175b9c0f1b6a831c399e269772661
// Output:
// 0cc175b9c0f1b6a831c399e269772661
}
```
@@ -1182,10 +1184,10 @@ import (
func main() {
md5Str := cryptor.Md5ByteWithBase64([]byte("hello"))
fmt.Println(md5Str)
fmt.Println(md5Str)
// Output:
// XUFAKrxLKna5cZ2REBfFkg==
// Output:
// XUFAKrxLKna5cZ2REBfFkg==
}
```
@@ -1268,10 +1270,10 @@ import (
func main() {
result := cryptor.Sha1WithBase64("hello")
fmt.Println(result)
fmt.Println(result)
// Output:
// qvTGHdzF6KLavt4PO0gs2a6pQ00=
// Output:
// qvTGHdzF6KLavt4PO0gs2a6pQ00=
}
```
@@ -1328,10 +1330,10 @@ import (
func main() {
result := cryptor.Sha256WithBase64("hello")
fmt.Println(result)
fmt.Println(result)
// Output:
// LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=
// Output:
// LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=
}
```
@@ -1388,10 +1390,10 @@ import (
func main() {
result := cryptor.Sha512WithBase64("hello")
fmt.Println(result)
fmt.Println(result)
// Output:
// m3HSJL1i83hdltRq0+o9czGb+8KJDKra4t/3JRlnPKcjI8PZm6XBHXx6zG4UuMXaDEZjR1wuXDre9G9zvN7AQw==
// Output:
// m3HSJL1i83hdltRq0+o9czGb+8KJDKra4t/3JRlnPKcjI8PZm6XBHXx6zG4UuMXaDEZjR1wuXDre9G9zvN7AQw==
}
```
@@ -1607,3 +1609,81 @@ func main() {
// hello world
}
```
### <span id="RsaSign">RsaSign</span>
<p>Signs the data with RSA algorithm.</p>
<b>Signature:</b>
```go
func RsaSign(hash crypto.Hash, data []byte, privateKeyFileName string) ([]byte, error)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
data := []byte("This is a test data for RSA signing")
hash := crypto.SHA256
privateKey := "./rsa_private.pem"
publicKey := "./rsa_public.pem"
signature, err := RsaSign(hash, data, privateKey)
if err != nil {
return
}
err = RsaVerifySign(hash, data, signature, publicKey)
if err != nil {
return
}
}
```
### <span id="RsaVerifySign">RsaVerifySign</span>
<p>Verifies the signature of the data with RSA algorithm.</p>
<b>Signature:</b>
```go
func RsaVerifySign(hash crypto.Hash, data, signature []byte, pubKeyFileName string) error
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/cryptor"
)
func main() {
data := []byte("This is a test data for RSA signing")
hash := crypto.SHA256
privateKey := "./rsa_private.pem"
publicKey := "./rsa_public.pem"
signature, err := RsaSign(hash, data, privateKey)
if err != nil {
return
}
err = RsaVerifySign(hash, data, signature, publicKey)
if err != nil {
return
}
}
```

View File

@@ -52,6 +52,7 @@ import (
- [ReadFile](#ReadFile)
- [ChunkRead](#ChunkRead)
- [ParallelChunkRead](#ParallelChunkRead)
- [GetExeOrDllVersion](#GetExeOrDllVersion)
<div STYLE="page-break-after: always;"></div>
@@ -559,7 +560,7 @@ import (
)
func main() {
err := fileutil.Zip("./test.zip", "./unzip/test.txt")
err := fileutil.UnZip("./test.zip", "./test.txt")
if err != nil {
fmt.Println(err)
}
@@ -1074,4 +1075,37 @@ func main() {
// Jim,21,male
// 2
}
```
### <span id="GetExeOrDllVersion">GetExeOrDllVersion</span>
<p>Get the version of exe or dll file on windows.</p>
<b>Signature:</b>
```go
func GetExeOrDllVersion(filePath string) (string, error)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/fileutil"
)
func main() {
v, err := fileutil.GetExeOrDllVersion(`C:\Program Files\Tencent\WeChat\WeChat.exe`)
if err != nil {
panic(err)
}
fmt.Println(v)
// Output:
// 3.9.10.19
}
```

View File

@@ -40,6 +40,7 @@ import (
- [RandStringSlice](#RandStringSlice)
- [RandBool](#RandBool)
- [RandBoolSlice](#RandBoolSlice)
- [RandNumberOfLength](#RandNumberOfLength)
<div STYLE="page-break-after: always;"></div>
@@ -523,4 +524,30 @@ func main() {
result := random.RandBoolSlice(2)
fmt.Println(result) // [true false] (random)
}
```
### <span id="RandNumberOfLength">RandNumberOfLength</span>
<p>Generates a random int number of specified length.</p>
<b>Signature:</b>
```go
func RandNumberOfLength(len int) int
```
<b>Signature:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/random"
)
func main() {
i := random.RandNumberOfLength(2)
fmt.Println(i) // 21 (random number of length 2)
}
```

View File

@@ -106,6 +106,10 @@ import (
- [RightPadding](#RightPadding)
- [LeftPadding](#LeftPadding)
- [Frequency](#Frequency)
- [JoinFunc](#JoinFunc)
- [ConcatBy](#ConcatBy)
<div STYLE="page-break-after: always;"></div>
@@ -2979,4 +2983,81 @@ func main() {
// Output:
// map[a:1 b:2 c:3]
}
```
### <span id="JoinFunc">JoinFunc</span>
<p>Joins the slice elements into a single string with the given separator.</p>
<b>Signature:</b>
```go
func JoinFunc[T any](slice []T, sep string, transform func(T) T) string
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
result := slice.JoinFunc([]string{"a", "b", "c"}, ", ", func(s string) string {
return strings.ToUpper(s)
})
fmt.Println(result)
// Output:
// A, B, C
}
```
### <span id="ConcatBy">ConcatBy</span>
<p>Concats the elements of a slice into a single value using the provided separator and connector function.</p>
<b>Signature:</b>
```go
func ConcatBy[T any](slice []T, sep T, connector func(T, T) T) T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/slice"
)
func main() {
type Person struct {
Name string
Age int
}
people := []Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
}
sep := Person{Name: " | ", Age: 0}
personConnector := func(a, b Person) Person {
return Person{Name: a.Name + b.Name, Age: a.Age + b.Age}
}
result := slice.ConcatBy(people, sep, personConnector)
fmt.Println(result.Name)
fmt.Println(result.Age)
// Output:
// Alice | Bob | Charlie
// 90
}
```

View File

@@ -35,6 +35,7 @@ import (
- [XError_Info](#XError_Info)
- [XError_Error](#XError_Error)
- [TryUnwrap](#TryUnwrap)
- [TryCatch](#TryCatch)
<div STYLE="page-break-after: always;"></div>
@@ -166,12 +167,12 @@ import (
func main() {
err1 := xerror.New("error").With("level", "high")
err2 := err1.Wrap(errors.New("invalid username"))
err2 := err1.Wrap(errors.New("invalid username"))
fmt.Println(err2.Error())
fmt.Println(err2.Error())
// Output:
// error: invalid username
// Output:
// error: invalid username
}
```
@@ -487,3 +488,56 @@ func main() {
// true
}
```
### <span id="TryCatch">TryCatch</span>
<p>Simple simulation of Java-style try-catch. It does not align with Go's error-handling philosophy. It is recommended to use it with caution.</p>
<b>Signature:</b>
```go
func NewTryCatch(ctx context.Context) *TryCatch
func (tc *TryCatch) Try(tryFunc func(ctx context.Context) error) *TryCatch
func (tc *TryCatch) Catch(catchFunc func(ctx context.Context, err error)) *TryCatch
func (tc *TryCatch) Finally(finallyFunc func(ctx context.Context)) *TryCatch
func (tc *TryCatch) Do()
```
<b>Example:<span style="float:right;display:inline-block;">[Run](todo)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/xerror"
)
func main() {
calledFinally := false
calledCatch := false
tc := xerror.NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
return errors.New("error message ")
}).Catch(func(ctx context.Context, err error) {
calledCatch = true
// Error in try block at /path/xxx.go:{line_number} - Cause: error message
// fmt.Println(err.Error())
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
fmt.Println(calledCatch)
fmt.Println(calledFinally)
// Output:
// true
// true
}
```

View File

@@ -14,6 +14,7 @@ import (
"encoding/csv"
"errors"
"fmt"
"github.com/duke-git/lancet/v2/validator"
"io"
"io/fs"
"net/http"
@@ -23,8 +24,6 @@ import (
"sort"
"strings"
"sync"
"github.com/duke-git/lancet/v2/validator"
)
// FileReader is a reader supporting offset seeking and reading one

View File

@@ -192,6 +192,27 @@ func ExampleListFileNames() {
// [assert.go assert_test.go error_join.go]
}
func ExampleMiMeType() {
fname := "./test.txt"
file, _ := os.Create(fname)
_, err := file.WriteString("hello\nworld")
if err != nil {
return
}
f, _ := os.Open(fname)
defer f.Close()
mimeType := MiMeType(f)
fmt.Println(mimeType)
os.Remove(fname)
// Output:
// application/octet-stream
}
func ExampleZip() {
srcFile := "./test.txt"
CreateFile(srcFile)
@@ -214,24 +235,20 @@ func ExampleZip() {
}
func ExampleUnZip() {
fname := "./test.txt"
file, _ := os.Create(fname)
zipFile := "./testdata/file.go.zip"
_, err := file.WriteString("hello\nworld")
err := UnZip(zipFile, "./testdata")
if err != nil {
return
}
f, _ := os.Open(fname)
defer f.Close()
exist := IsExist("./testdata/file.go")
fmt.Println(exist)
mimeType := MiMeType(f)
fmt.Println(mimeType)
os.Remove(fname)
os.Remove("./testdata/file.go")
// Output:
// application/octet-stream
// true
}
func ExampleZipAppendEntry() {

83
fileutil/file_windows.go Normal file
View File

@@ -0,0 +1,83 @@
//go:build windows
package fileutil
import (
"fmt"
"syscall"
"unsafe"
)
// tagVS_FIXEDFILEINFO 参考结构体https://learn.microsoft.com/zh-cn/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
type tagVS_FIXEDFILEINFO struct {
Signature uint32
StructVersion uint32
FileVersionMS uint32
FileVersionLS uint32
ProductVersionMS uint32
ProductVersionLS uint32
FileFlagsMask uint32
FileFlags uint32
FileOS uint32
FileType uint32
FileSubtype uint32
FileDateMS uint32
FileDateLS uint32
}
// GetExeOrDllVersion get the version of exe or dll file on windows.
// Play: todo
func GetExeOrDllVersion(filePath string) (string, error) {
// 加载系统dll
versionDLL := syscall.NewLazyDLL("version.dll")
getFileVersionInfoSize := versionDLL.NewProc("GetFileVersionInfoSizeW")
getFileVersionInfo := versionDLL.NewProc("GetFileVersionInfoW")
verQueryValue := versionDLL.NewProc("VerQueryValueW")
// 转换路径为UTF-16
filePathPtr, err := syscall.UTF16PtrFromString(filePath)
if err != nil {
return "", fmt.Errorf("unable to convert file path to UTF-16: %w", err)
}
// 获取version信息大小
size, _, err := getFileVersionInfoSize.Call(
uintptr(unsafe.Pointer(filePathPtr)),
0,
)
if size == 0 {
return "", fmt.Errorf("unable to obtain version information size: %v", err)
}
// 加载version信息
data := make([]byte, size)
ret, _, err := getFileVersionInfo.Call(uintptr(unsafe.Pointer(filePathPtr)), 0, size, uintptr(unsafe.Pointer(&data[0])))
if ret == 0 {
return "", fmt.Errorf("unable to obtain version information: %v", err)
}
// 查询version信息
var fixedInfo *tagVS_FIXEDFILEINFO
var fixedInfoLen uint32
u16, err := syscall.UTF16PtrFromString(`\`)
if err != nil {
return "", fmt.Errorf("unable to convert file path to UTF-16: %w", err)
}
ret, _, err = verQueryValue.Call(
uintptr(unsafe.Pointer(&data[0])),
uintptr(unsafe.Pointer(u16)),
uintptr(unsafe.Pointer(&fixedInfo)),
uintptr(unsafe.Pointer(&fixedInfoLen)),
)
if ret == 0 {
return "", fmt.Errorf("unable to query version information: %v", err)
}
// 转换结构体
major := fixedInfo.FileVersionMS >> 16
minor := fixedInfo.FileVersionMS & 0xFFFF
build := fixedInfo.FileVersionLS >> 16
revision := fixedInfo.FileVersionLS & 0xFFFF
return fmt.Sprintf("%d.%d.%d.%d", major, minor, build, revision), nil
}

View File

@@ -0,0 +1,13 @@
//go:build windows
package fileutil
import "testing"
func TestGetExeOrDllVersion(t *testing.T) {
v, err := GetExeOrDllVersion(`C:\Windows\System32\cmd.exe`)
if err != nil {
t.Error(err)
}
t.Log(v)
}

2
go.sum
View File

@@ -1,6 +1,4 @@
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw=
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=

View File

@@ -35,7 +35,7 @@ func (om *OrderedMap[K, V]) Set(key K, value V) {
defer om.mu.Unlock()
if elem, ok := om.index[key]; ok {
elem.Value = value
om.data[key] = value
om.order.MoveToBack(elem)
return

View File

@@ -19,6 +19,10 @@ func TestOrderedMap_Set_Get(t *testing.T) {
assert.Equal(1, val)
assert.Equal(true, ok)
om.Set("a", 2)
val, _ = om.Get("a")
assert.Equal(2, val)
val, ok = om.Get("d")
assert.Equal(false, ok)
assert.Equal(0, val)

View File

@@ -221,6 +221,22 @@ func (client *HttpClient) SendRequest(request *HttpRequest) (*http.Response, err
return resp, nil
}
// AsyncSendRequest send http request with goroutine, pop response and error to channels
func (client *HttpClient) AsyncSendRequest(request *HttpRequest, respChan chan *http.Response, errChan chan error) {
go func() {
defer func() {
if err := recover(); err != nil {
errChan <- fmt.Errorf("%v", err)
}
}()
resp, err := client.SendRequest(request)
if err != nil {
errChan <- err
}
respChan <- resp
}()
}
// DecodeResponse decode response into target object.
// Play: https://go.dev/play/p/jUSgynekH7G
func (client *HttpClient) DecodeResponse(resp *http.Response, target any) error {

View File

@@ -373,6 +373,45 @@ func TestSendRequestWithFilePath(t *testing.T) {
}
}
func TestHttpClient_AsyncSendRequest(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestHttpClient_Get")
request := &HttpRequest{
RawURL: "https://jsonplaceholder.typicode.com/todos/1",
Method: "GET",
}
httpClient := NewHttpClient()
respCh := make(chan *http.Response, 20)
errCh := make(chan error, 20)
for i := 0; i < 50; i++ {
httpClient.AsyncSendRequest(request, respCh, errCh)
}
for i := 0; i < 50; i++ {
select {
case resp := <-respCh:
type Todo struct {
UserId int `json:"userId"`
Id int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
var todo Todo
err := httpClient.DecodeResponse(resp, &todo)
if err != nil {
t.FailNow()
}
assert.Equal(1, todo.Id)
case err := <-errCh:
if err != nil {
t.Log("net error: ", err.Error())
}
}
}
}
func TestProxy(t *testing.T) {
config := &HttpClientConfig{
HandshakeTimeout: 20 * time.Second,

View File

@@ -327,3 +327,13 @@ func UUIdV4() (string, error) {
return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
}
// RandNumberOfLength 生成一个长度为len的随机数
// Play: todo
func RandNumberOfLength(len int) int {
m := int(math.Pow10(len) - 1)
i := int(math.Pow10(len - 1))
ret := rand.Intn(m-i+1) + i
return ret
}

View File

@@ -3,6 +3,7 @@ package random
import (
"reflect"
"regexp"
"strconv"
"testing"
"github.com/duke-git/lancet/v2/internal"
@@ -361,3 +362,9 @@ func TestRandBoolSlice(t *testing.T) {
}
})
}
func TestRandNumberOfLength(t *testing.T) {
t.Parallel()
randi := RandNumberOfLength(6)
assert := internal.NewAssert(t, "TestRandNumberOfLength")
assert.Equal(6, len(strconv.Itoa(randi)))
}

View File

@@ -1398,3 +1398,35 @@ func Frequency[T comparable](slice []T) map[T]int {
return result
}
// JoinFunc joins the slice elements into a single string with the given separator.
// Play: todo
func JoinFunc[T any](slice []T, sep string, transform func(T) T) string {
var buf strings.Builder
for i, v := range slice {
if i > 0 {
buf.WriteString(sep)
}
buf.WriteString(fmt.Sprint(transform(v)))
}
return buf.String()
}
// ConcatBy concats the elements of a slice into a single value using the provided separator and connector function.
// Play: todo
func ConcatBy[T any](slice []T, sep T, connector func(T, T) T) T {
var result T
if len(slice) == 0 {
return result
}
for i, v := range slice {
result = connector(result, v)
if i < len(slice)-1 {
result = connector(result, sep)
}
}
return result
}

View File

@@ -195,18 +195,14 @@ func UniqueByConcurrent[T comparable](slice []T, comparator func(item T, other T
chunks = append(chunks, slice[i:end])
}
type resultChunk struct {
index int
data []T
}
resultCh := make(chan resultChunk, numThreads)
resultCh := make(chan resultChunk[T], numThreads)
var wg sync.WaitGroup
for i, chunk := range chunks {
wg.Add(1)
go func(index int, chunk []T) {
defer wg.Done()
resultCh <- resultChunk{index, removeDuplicate(chunk, comparator)}
resultCh <- resultChunk[T]{index, removeDuplicate(chunk, comparator)}
}(i, chunk)
}

View File

@@ -1261,3 +1261,42 @@ func ExampleFrequency() {
// Output:
// map[a:1 b:2 c:3]
}
func ExampleJoinFunc() {
result := JoinFunc([]string{"a", "b", "c"}, ", ", func(s string) string {
return strings.ToUpper(s)
})
fmt.Println(result)
// Output:
// A, B, C
}
func ExampleConcatBy() {
type Person struct {
Name string
Age int
}
people := []Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
}
sep := Person{Name: " | ", Age: 0}
personConnector := func(a, b Person) Person {
return Person{Name: a.Name + b.Name, Age: a.Age + b.Age}
}
result := ConcatBy(people, sep, personConnector)
fmt.Println(result.Name)
fmt.Println(result.Age)
// Output:
// Alice | Bob | Charlie
// 90
}

View File

@@ -7,6 +7,13 @@ import (
"golang.org/x/exp/constraints"
)
// resultChunk is used to store the intermediate results of UniqueByConcurrent.
// It is defined separately to be compatible with versions of go up to 1.20.
type resultChunk[T comparable] struct {
index int
data []T
}
// sliceValue return the reflect value of a slice
func sliceValue(slice any) reflect.Value {
v := reflect.ValueOf(slice)

View File

@@ -1816,3 +1816,81 @@ func TestFrequency(t *testing.T) {
assert.Equal(expected, result)
})
}
func TestJoinFunc(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestJoinFunc")
t.Run("basic case", func(t *testing.T) {
result := JoinFunc([]int{1, 2, 3}, ", ", func(i int) int {
return i * 2
})
expected := "2, 4, 6"
assert.Equal(expected, result)
})
t.Run("empty slice", func(t *testing.T) {
result := JoinFunc([]int{}, ", ", func(i int) int {
return i * 2
})
assert.Equal("", result)
})
t.Run("single element slice", func(t *testing.T) {
result := JoinFunc([]int{1}, ", ", func(i int) int {
return i * 2
})
assert.Equal("2", result)
})
t.Run("string slice", func(t *testing.T) {
result := JoinFunc([]string{"a", "b", "c"}, ", ", func(s string) string {
return strings.ToUpper(s)
})
expected := "A, B, C"
assert.Equal(expected, result)
})
}
func TestConcatBy(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestConcatBy")
t.Run("Join strings", func(t *testing.T) {
result := ConcatBy([]string{"Hello", "World"}, ", ", func(a, b string) string {
return a + b
})
expected := "Hello, World"
assert.Equal(expected, result)
})
t.Run("Join Person struct", func(t *testing.T) {
type Person struct {
Name string
Age int
}
people := []Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
}
sep := Person{Name: " | ", Age: 0}
personConnector := func(a, b Person) Person {
return Person{Name: a.Name + b.Name, Age: a.Age + b.Age}
}
result := ConcatBy(people, sep, personConnector)
assert.Equal("Alice | Bob | Charlie", result.Name)
assert.Equal(90, result.Age)
})
}

100
xerror/trycatch.go Normal file
View File

@@ -0,0 +1,100 @@
package xerror
import (
"context"
"fmt"
"runtime"
)
// TryCatch is a struct to handle try-catch-finally block.
// This implementation is merely a simulation of Java-style try-catch and does not align with Go's error-handling philosophy. It is recommended to use it with caution.
type TryCatch struct {
ctx context.Context
tryFunc func(ctx context.Context) error
catchFunc func(ctx context.Context, err error)
finallyFunc func(ctx context.Context)
}
// NewTryCatch creates a new TryCatch instance.
func NewTryCatch(ctx context.Context) *TryCatch {
return &TryCatch{
ctx: ctx,
}
}
// Try sets the try function.
func (tc *TryCatch) Try(tryFunc func(ctx context.Context) error) *TryCatch {
tc.tryFunc = tryFunc
return tc
}
// Catch sets the catch function.
func (tc *TryCatch) Catch(catchFunc func(ctx context.Context, err error)) *TryCatch {
tc.catchFunc = catchFunc
return tc
}
// Finally sets the finally function.
func (tc *TryCatch) Finally(finallyFunc func(ctx context.Context)) *TryCatch {
tc.finallyFunc = finallyFunc
return tc
}
// Do executes the try-catch-finally block.
func (tc *TryCatch) Do() {
defer func() {
if r := recover(); r != nil {
if tc.catchFunc != nil {
err := fmt.Errorf("panic: %v", r)
tc.catchFunc(tc.ctx, WrapCatchError(err, "Recovered from panic"))
}
}
if tc.finallyFunc != nil {
tc.finallyFunc(tc.ctx)
}
}()
if tc.ctx.Err() != nil {
if tc.catchFunc != nil {
tc.catchFunc(tc.ctx, WrapCatchError(tc.ctx.Err(), "Context cancelled or timed out"))
}
return
}
if tc.tryFunc != nil {
if err := tc.tryFunc(tc.ctx); err != nil {
if tc.catchFunc != nil {
tc.catchFunc(tc.ctx, WrapCatchError(err, "Error in try block"))
}
}
}
}
// CatchError is an error type to handle try-catch-finally block.
type CatchError struct {
Msg string
File string
Line int
Cause error
}
// Error returns the error message.
func (e *CatchError) Error() string {
return fmt.Sprintf("%s at %s:%d - Cause: %v", e.Msg, e.File, e.Line, e.Cause)
}
// WrapCatchError wraps an error with message, file, and line.
func WrapCatchError(err error, msg string) *CatchError {
_, file, line, ok := runtime.Caller(2)
if !ok {
file = "unknown"
line = 0
}
return &CatchError{
Msg: msg,
File: file,
Line: line,
Cause: err,
}
}

172
xerror/trycatch_test.go Normal file
View File

@@ -0,0 +1,172 @@
package xerror
import (
"context"
"errors"
"testing"
"github.com/duke-git/lancet/v2/internal"
)
func TestTryCatchSuccess(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchSuccess")
counter := 0
calledCatch := false
calledFinally := false
tc := NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
counter++
return nil
}).Catch(func(ctx context.Context, err error) {
calledCatch = true
t.Errorf("Catch should not be called")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal(1, counter)
assert.Equal(false, calledCatch)
assert.Equal(true, calledFinally)
}
func TestTryCatchError(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchError")
var catchedError error
calledFinally := false
tc := NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
return errors.New("error")
}).Catch(func(ctx context.Context, err error) {
catchedError = errors.New("catched error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal("catched error", catchedError.Error())
assert.Equal(true, calledFinally)
}
func TestTryCatchPanic(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchPanic")
var catchedError error
calledFinally := false
tc := NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
panic("panic info")
}).Catch(func(ctx context.Context, err error) {
catchedError = errors.New("catched error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal("catched error", catchedError.Error())
assert.Equal(true, calledFinally)
}
func TestTryCatchContextCancelled(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchContextCancelled")
var catchedError error
calledFinally := false
ctx, cancel := context.WithCancel(context.Background())
cancel()
tc := NewTryCatch(ctx)
tc.Try(func(ctx context.Context) error {
return nil
}).Catch(func(ctx context.Context, err error) {
catchedError = errors.New("catched error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal("catched error", catchedError.Error())
assert.Equal(true, calledFinally)
}
func TestTryCatchContextTimeout(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchContextTimeout")
var catchedError error
calledFinally := false
ctx, cancel := context.WithTimeout(context.Background(), 0)
defer cancel()
tc := NewTryCatch(ctx)
tc.Try(func(ctx context.Context) error {
return nil
}).Catch(func(ctx context.Context, err error) {
catchedError = errors.New("catched error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal("catched error", catchedError.Error())
assert.Equal(true, calledFinally)
}
func TestTryCatchContextError(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchContextError")
var catchedError error
calledFinally := false
ctx, cancel := context.WithTimeout(context.Background(), 0)
defer cancel()
tc := NewTryCatch(ctx)
tc.Try(func(ctx context.Context) error {
return errors.New("error")
}).Catch(func(ctx context.Context, err error) {
catchedError = errors.New("catched error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal("catched error", catchedError.Error())
assert.Equal(true, calledFinally)
}
func TestTryCatchNoCatch(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestTryCatchNoCatch")
calledFinally := false
tc := NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
return errors.New("error")
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
assert.Equal(true, calledFinally)
}

View File

@@ -1,6 +1,7 @@
package xerror
import (
"context"
"errors"
"fmt"
"reflect"
@@ -61,7 +62,7 @@ func ExampleXError_StackTrace() {
// Output:
// github.com/duke-git/lancet/v2/xerror.ExampleXError_StackTrace
// 52
// 53
// true
}
@@ -154,3 +155,27 @@ func ExampleTryUnwrap() {
// 42
// true
}
func ExampleTryCatch() {
calledFinally := false
calledCatch := false
tc := NewTryCatch(context.Background())
tc.Try(func(ctx context.Context) error {
return errors.New("error message")
}).Catch(func(ctx context.Context, err error) {
calledCatch = true
// Error in try block at /path/xxx.go:174 - Cause: error message
// fmt.Println(err.Error())
}).Finally(func(ctx context.Context) {
calledFinally = true
}).Do()
fmt.Println(calledCatch)
fmt.Println(calledFinally)
// Output:
// true
// true
}