Oops! See if restyv2 response is retryable.
Structured HTTP operation fault classification with retryable semantics, designed with go-resty/resty/v2.
🎯 Fault Classification: Categorize HTTP response outcomes into actionable categories ⚡ Retryable Detection: Determine if an operation is retryable with sensible defaults 🔄 Configurable Settings: Customize settings based on status code and kind 🔍 Content Checks: Custom content checks handling unique cases (captcha, WAF, business codes) ⏱️ Wait Time: Suggested wait duration before retrying
go get github.com/yylego/restyoopspackage main
import (
"fmt"
"github.com/go-resty/resty/v2"
"github.com/yylego/restyoops"
)
func main() {
client := resty.New()
detective := restyoops.NewDetective(restyoops.NewConfig())
resp, oops := detective.Detect(client.R().Get("https://api.example.com/data"))
if oops != nil {
fmt.Printf("Kind: %s, Retryable: %v\n", oops.Kind, oops.Retryable)
if oops.IsRetryable() {
fmt.Printf("Wait before retrying: %v\n", oops.WaitTime)
}
return
}
fmt.Println("Request success!")
fmt.Println("Response:", string(resp.Body()))
}Detective wraps config and provides a convenient API that accepts resty's values without intermediate steps:
type OopsIssue = Oops
detective := restyoops.NewDetective(restyoops.NewConfig())
resp, oops := detective.Detect(client.R().Get(url)) // No need: resp, err := ...; then Detect(..., resp, err)
if oops != nil {
// handle issue
return
}
// success
data := resp.Body()Advantage: Avoids the resp, err := client.R().Get(url) then Detect(cfg, resp, err) pattern.
| Kind | Description | Default Retryable |
|---|---|---|
KindNetwork |
Network issues (timeout, DNS, TCP, TLS) | true |
KindHttp |
HTTP 4xx/5xx status codes | varies |
KindParse |
Response parsing failed | false |
KindBlock |
Request blocked (captcha, WAF) | false |
KindBusiness |
Business logic issue (HTTP 200, code!=0) | false |
KindUnknown |
Unclassified issues | false |
Note: Success returns nil (no oops means no problem).
| Status Code | Retryable |
|---|---|
| 408 Request Timeout | true |
| 429 Too Many Requests | true |
| 500 Internal Server Err | true |
| 502 Bad Gateway | true |
| 503 Service Unavailable | true |
| 504 Gateway Timeout | true |
| 400 Bad Request | false |
| 401 Unauthorized | false |
| 403 Forbidden | false |
| 404 Not Found | false |
| 409 Conflict | false |
| 422 Unprocessable Entity | false |
| Other 5xx | true |
| Other 4xx | false |
When detecting, configurations are applied in the following sequence (highest to lowest):
- ContentChecks - Custom content check functions (checked first)
- StatusOptions - Status code specific configuration
- KindOptions - Kind specific configuration
- Default - Built-in default values
When a high-precedence config matches, others below it are skipped.
cfg := restyoops.NewConfig().
WithStatusRetryable(403, true, 5*time.Second). // Make 403 retryable
WithStatusRetryable(500, false, 0) // Make 500 not retryable
oops := restyoops.Detect(cfg, resp, err)cfg := restyoops.NewConfig().
WithKindRetryable(restyoops.KindNetwork, true, 10*time.Second)
oops := restyoops.Detect(cfg, resp, err)cfg := restyoops.NewConfig().
WithContentCheck(200, func(contentType string, content []byte) *restyoops.Oops {
if bytes.Contains(content, []byte("captcha")) {
return restyoops.NewOops(restyoops.KindBlock, 200, errors.New("CAPTCHA DETECTED"), true).WithWaitTime(5*time.Second)
}
return nil // pass, continue default detection
})
oops := restyoops.Detect(cfg, resp, err)cfg := restyoops.NewConfig().
WithDefaultWait(2 * time.Second)
oops := restyoops.Detect(cfg, resp, err)type Oops struct {
Kind Kind // Classification
StatusCode int // HTTP status code
ContentType string // Response Content-Type
Cause error // Wrapped cause (never nil)
Retryable bool // Can be resolved via retries
WaitTime time.Duration // Suggested wait time
}func Detect(cfg *Config, resp *resty.Response, respCause error) *OopsUsage:
resp, err := client.R().Get(url)
oops := restyoops.Detect(restyoops.NewConfig(), resp, err)MIT License - see LICENSE.
Contributions are welcome! Report bugs, suggest features, and contribute code:
- 🐛 Mistake reports? Open an issue on GitHub with reproduction steps
- 💡 Fresh ideas? Create an issue to discuss
- 📖 Documentation confusing? Report it so we can improve
- 🚀 Need new features? Share the use cases to help us understand requirements
- ⚡ Performance issue? Help us optimize through reporting slow operations
- 🔧 Configuration problem? Ask questions about complex setups
- 📢 Follow project progress? Watch the repo to get new releases and features
- 🌟 Success stories? Share how this package improved the workflow
- 💬 Feedback? We welcome suggestions and comments
New code contributions, follow this process:
- Fork: Fork the repo on GitHub (using the webpage UI).
- Clone: Clone the forked project (
git clone https://github.com/yourname/repo-name.git). - Navigate: Navigate to the cloned project (
cd repo-name) - Branch: Create a feature branch (
git checkout -b feature/xxx). - Code: Implement the changes with comprehensive tests
- Testing: (Golang project) Ensure tests pass (
go test ./...) and follow Go code style conventions - Documentation: Update documentation to support client-facing changes
- Stage: Stage changes (
git add .) - Commit: Commit changes (
git commit -m "Add feature xxx") ensuring backward compatible code - Push: Push to the branch (
git push origin feature/xxx). - PR: Open a merge request on GitHub (on the GitHub webpage) with detailed description.
Please ensure tests pass and include relevant documentation updates.
Welcome to contribute to this project via submitting merge requests and reporting issues.
Project Support:
- ⭐ Give GitHub stars if this project helps you
- 🤝 Share with teammates and (golang) programming friends
- 📝 Write tech blogs about development tools and workflows - we provide content writing support
- 🌟 Join the ecosystem - committed to supporting open source and the (golang) development scene
Have Fun Coding with this package! 🎉🎉🎉