{"id":32948819,"url":"https://github.com/slipros/roamer","last_synced_at":"2026-01-14T15:37:58.562Z","repository":{"id":169872824,"uuid":"641600815","full_name":"slipros/roamer","owner":"slipros","description":"Fast and flexible HTTP request parser for Go","archived":false,"fork":false,"pushed_at":"2025-11-10T02:04:50.000Z","size":559,"stargazers_count":5,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-11-12T22:02:49.654Z","etag":null,"topics":["api","data-binding","form-parsing","go","go-api","go-http","go-http-requests","go-library","golang","http","http-request","http-request-params-reader","json","middleware","parser","request-parsing","rest-api","struct-tags","web-framework"],"latest_commit_sha":null,"homepage":"https://slipros.github.io/roamer/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/slipros.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-05-16T20:17:21.000Z","updated_at":"2025-10-17T15:41:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"ff3ad30a-baab-46c0-9ffe-f4e6b92903ce","html_url":"https://github.com/slipros/roamer","commit_stats":null,"previous_names":["slipros/roamer"],"tags_count":36,"template":false,"template_full_name":null,"purl":"pkg:github/slipros/roamer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slipros%2Froamer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slipros%2Froamer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slipros%2Froamer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slipros%2Froamer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/slipros","download_url":"https://codeload.github.com/slipros/roamer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slipros%2Froamer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28424374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T13:30:50.153Z","status":"ssl_error","status_checked_at":"2026-01-14T13:29:08.907Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","data-binding","form-parsing","go","go-api","go-http","go-http-requests","go-library","golang","http","http-request","http-request-params-reader","json","middleware","parser","request-parsing","rest-api","struct-tags","web-framework"],"created_at":"2025-11-12T20:00:35.664Z","updated_at":"2026-01-14T15:37:58.534Z","avatar_url":"https://github.com/slipros.png","language":"Go","funding_links":[],"categories":["Forms","表单"],"sub_categories":["Search and Analytic Databases","检索及分析资料库"],"readme":"# roamer\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/slipros/roamer)](https://goreportcard.com/report/github.com/slipros/roamer)\n[![Build Status](https://github.com/slipros/roamer/actions/workflows/test.yml/badge.svg)](https://github.com/slipros/roamer/actions)\n[![Coverage Status](https://coveralls.io/repos/github/slipros/roamer/badge.svg)](https://coveralls.io/github/slipros/roamer)\n[![Go Reference](https://pkg.go.dev/badge/github.com/slipros/roamer.svg)](https://pkg.go.dev/github.com/slipros/roamer)\n[![Go Version](https://img.shields.io/github/go-mod/go-version/slipros/roamer)](https://github.com/slipros/roamer)\n[![GitHub release](https://img.shields.io/github/v/release/slipros/roamer.svg)](https://github.com/slipros/roamer/releases)\n[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)\n\nRoamer is a flexible, extensible HTTP request parser for Go that makes handling and extracting data from HTTP requests effortless. It provides a declarative way to map HTTP request data to Go structs using struct tags.\n\n```mermaid\ngraph TD\n    subgraph \"Input\"\n        A[HTTP Request]\n    end\n\n    subgraph \"Data Sources\"\n        B1[Headers]\n        B2[Cookies]\n        B3[Query Params]\n        B4[Path Variables]\n        B5[Request Body]\n        B6[Custom]\n    end\n\n    subgraph \"Roamer Core Engine\"\n        direction LR\n        P[Parsers]\n        D[Decoders]\n        F[Formatters]\n    end\n\n    subgraph \"Output\"\n        E[Populated Go Struct]\n    end\n\n    A --\u003e B1 \u0026 B2 \u0026 B3 \u0026 B4 \u0026 B5 \u0026 B6\n\n    B1 \u0026 B2 \u0026 B3 \u0026 B4 \u0026 B6 -- values for --\u003e P\n    B5 -- content for --\u003e D\n\n    P -- parsed data --\u003e F\n    D -- decoded data --\u003e F\n\n    F -- formatted values --\u003e E\n\n    classDef source stroke:#d4ac0d,stroke-width:4px\n    classDef core stroke:#0097c0,stroke-width:4px\n    classDef io stroke:#333,stroke-width:4px\n    class A,E io\n    class B1,B2,B3,B4,B5,B6 source\n    class P,D,F core\n```\n\n## Features\n\n- **Multiple data sources**: Parse data from HTTP headers, cookies, query parameters, path variables, and request body\n- **Content-type based decoding**: Automatically decode JSON, XML, form data, and multipart forms\n- **Default Values**: Set default values for fields using the `default` tag\n- **Formatters**: Transform parsed data (trim strings, apply numeric constraints, handle time zones, manipulate slices)\n- **Router integration**: Built-in support for Chi, Gorilla Mux, and HttpRouter\n- **Type conversion**: Automatic conversion of string values to appropriate Go types\n- **Extensibility**: Easily create custom parsers, decoders, and formatters\n- **Middleware support**: Convenient middleware for integrating with HTTP handlers\n- **Body preservation**: Read request body multiple times when needed\n\n## Installation\n\n```bash\ngo get -u github.com/slipros/roamer@latest\n```\n\nFor router integrations:\n\n```bash\n# Chi router\ngo get -u github.com/slipros/roamer/pkg/chi@latest\n\n# Gorilla Mux\ngo get -u github.com/slipros/roamer/pkg/gorilla@latest\n\n# HttpRouter\ngo get -u github.com/slipros/roamer/pkg/httprouter@latest\n```\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/slipros/roamer\"\n\t\"github.com/slipros/roamer/decoder\"\n\t\"github.com/slipros/roamer/formatter\"\n\t\"github.com/slipros/roamer/parser\"\n)\n\n// Define request struct with tags\ntype CreateUserRequest struct {\n\tName      string `json:\"name\" string:\"trim_space\"`\n\tEmail     string `json:\"email\" string:\"trim_space,lower\"`\n\tAge       int    `query:\"age\" numeric:\"min=18\"`\n\tUserAgent string `header:\"User-Agent\"`\n}\n\nfunc main() {\n\t// Initialize roamer\n\tr := roamer.NewRoamer(\n\t\troamer.WithDecoders(decoder.NewJSON()),\n\t\troamer.WithParsers(\n\t\t\tparser.NewHeader(),\n\t\t\tparser.NewQuery(),\n\t\t),\n\t\troamer.WithFormatters(\n\t\t\tformatter.NewString(),\n\t\t\tformatter.NewNumeric(),\n\t\t),\n\t)\n\n\t// Create handler\n\thttp.HandleFunc(\"/users\", func(w http.ResponseWriter, req *http.Request) {\n\t\tvar userReq CreateUserRequest\n\n\t\t// Parse request\n\t\tif err := r.Parse(req, \u0026userReq); err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\t// Use parsed data\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tif err := json.NewEncoder(w).Encode(map[string]any{\n\t\t\t\"name\":       userReq.Name,\n\t\t\t\"email\":      userReq.Email,\n\t\t\t\"age\":        userReq.Age,\n\t\t\t\"user_agent\": userReq.UserAgent,\n\t\t}); err != nil {\n\t\t\tlog.Printf(\"Failed to encode response: %v\", err)\n\t\t\thttp.Error(w, \"Failed to encode response\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t})\n\n\tlog.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n```\n\n## Examples\n\nComprehensive examples are available in the [examples/](examples/) directory:\n\n### Basic Usage\n- [**basic/cmd/simple**](examples/basic/cmd/simple/) - Simple request parsing\n- [**basic/cmd/middleware**](examples/basic/cmd/middleware/) - Using roamer as middleware\n\n### Router Integration\n- [**chi_router/**](examples/chi_router/) - Chi router integration with path parameters\n- [**gorilla_router/**](examples/gorilla_router/) - Gorilla Mux integration\n- [**httprouter/**](examples/httprouter/) - HttpRouter integration\n\n### Advanced Features\n- [**formatters/**](examples/formatters/) - String, numeric, time, and slice formatters\n- [**multipart/**](examples/multipart/) - File upload handling\n- [**custom_parser/**](examples/custom_parser/) - Custom parser for extracting data from request context\n- [**custom_decoder/**](examples/custom_decoder/) - Custom YAML decoder implementation\n- [**custom_formatter/**](examples/custom_formatter/) - Custom phone number formatter\n- [**body_preservation/**](examples/body_preservation/) - Reading request body multiple times\n\nSee the [examples README](examples/README.md) for a complete list and how to run them.\n\n## Struct Tags Reference\n\n### Data Source Tags\n\n| Tag | Description | Example |\n|-----|-------------|---------|\n| `json` | Parse from JSON body | `json:\"name\"` |\n| `xml` | Parse from XML body | `xml:\"name\"` |\n| `form` | Parse from URL-encoded form | `form:\"name\"` |\n| `multipart` | Parse from multipart form | `multipart:\"file\"` |\n| `query` | Parse from query parameters | `query:\"page\"` |\n| `header` | Parse from HTTP headers | `header:\"User-Agent\"` |\n| `cookie` | Parse from cookies | `cookie:\"session_id\"` |\n| `path` | Parse from path variables | `path:\"id\"` |\n| `default` | Default value if not present | `default:\"1\"` |\n\n### Formatter Tags\n\n| Tag | Operations | Example |\n|-----|------------|---------|\n| `string` | `trim_space`, `lower`, `upper`, `title`, `snake`, `camel`, `kebab`, `slug` | `string:\"trim_space,lower\"` |\n| `numeric` | `min=N`, `max=N`, `abs`, `round`, `ceil`, `floor` | `numeric:\"min=0,max=100\"` |\n| `time` | `timezone=TZ`, `truncate=UNIT`, `start_of_day`, `end_of_day` | `time:\"timezone=UTC\"` |\n| `slice` | `unique`, `sort`, `sort_desc`, `compact`, `limit=N` | `slice:\"unique,sort\"` |\n\n## Creating Extensions\n\n### Custom Parser\n\n```go\ntype CustomParser struct{}\n\nfunc (p *CustomParser) Parse(r *http.Request, tag reflect.StructTag, _ parser.Cache) (any, bool) {\n\ttagValue, ok := tag.Lookup(\"custom\")\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\t// Extract and return value\n\treturn value, true\n}\n\nfunc (p *CustomParser) Tag() string {\n\treturn \"custom\"\n}\n```\n\n### Custom Decoder\n\n```go\ntype CustomDecoder struct{}\n\nfunc (d *CustomDecoder) Decode(r *http.Request, ptr any) error {\n\t// Decode request body into ptr\n\treturn nil\n}\n\nfunc (d *CustomDecoder) ContentType() string {\n\treturn \"application/custom\"\n}\n```\n\n### Custom Formatter\n\n```go\ntype CustomFormatter struct{}\n\nfunc (f *CustomFormatter) Format(tag reflect.StructTag, ptr any) error {\n\ttagValue, ok := tag.Lookup(\"custom_format\")\n\tif !ok {\n\t\treturn nil\n\t}\n\t// Format the value pointed to by ptr\n\treturn nil\n}\n\nfunc (f *CustomFormatter) Tag() string {\n\treturn \"custom_format\"\n}\n```\n\nSee the [examples/](examples/) directory for complete custom extension examples.\n\n## Performance\n\nRoamer is designed for production use with:\n- Efficient reflection techniques\n- Caching for improved performance\n- Optional `sync.Pool` support for high-throughput applications\n- Minimal allocations in hot paths\n\n## Best Practices\n\n1. **Separate request and response structs** - Use dedicated structs for parsing requests\n2. **Endpoint-specific structs** - Create tailored structs for each endpoint to minimize overhead\n3. **Use formatters** - Let roamer handle common transformations (trimming, case conversion, etc.)\n4. **Combine with validation** - Use roamer for parsing, then validate with libraries like [validator.go](https://github.com/raoptimus/validator.go)\n\n## Documentation\n\n- [Go Package Documentation](https://pkg.go.dev/github.com/slipros/roamer)\n- [Examples](examples/)\n- [Changelog](CHANGELOG.md)\n\n## Contributing\n\nContributions are welcome! Please submit issues or pull requests.\n\n## License\n\nRoamer is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslipros%2Froamer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fslipros%2Froamer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslipros%2Froamer/lists"}