{"id":36454527,"url":"https://github.com/tphakala/go-remedy","last_synced_at":"2026-01-11T23:01:38.652Z","repository":{"id":329161147,"uuid":"1118370138","full_name":"tphakala/go-remedy","owner":"tphakala","description":"Go client library for BMC Remedy AR System REST API","archived":false,"fork":false,"pushed_at":"2025-12-17T17:36:12.000Z","size":34,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-21T05:58:33.517Z","etag":null,"topics":["api-client","bmc-remedy","go","golang","remedy","rest-api"],"latest_commit_sha":null,"homepage":null,"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/tphakala.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2025-12-17T16:46:12.000Z","updated_at":"2025-12-17T17:39:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tphakala/go-remedy","commit_stats":null,"previous_names":["tphakala/go-remedy"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/tphakala/go-remedy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tphakala%2Fgo-remedy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tphakala%2Fgo-remedy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tphakala%2Fgo-remedy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tphakala%2Fgo-remedy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tphakala","download_url":"https://codeload.github.com/tphakala/go-remedy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tphakala%2Fgo-remedy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28326166,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T22:11:01.104Z","status":"ssl_error","status_checked_at":"2026-01-11T22:10:58.990Z","response_time":60,"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-client","bmc-remedy","go","golang","remedy","rest-api"],"created_at":"2026-01-11T23:01:38.587Z","updated_at":"2026-01-11T23:01:38.639Z","avatar_url":"https://github.com/tphakala.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-remedy\n\nA native Go client library for the BMC Remedy AR System REST API.\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/tphakala/go-remedy.svg)](https://pkg.go.dev/github.com/tphakala/go-remedy)\n[![Go Report Card](https://goreportcard.com/badge/github.com/tphakala/go-remedy)](https://goreportcard.com/report/github.com/tphakala/go-remedy)\n[![CI](https://github.com/tphakala/go-remedy/actions/workflows/ci.yml/badge.svg)](https://github.com/tphakala/go-remedy/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Go Version](https://img.shields.io/github/go-mod/go-version/tphakala/go-remedy)](https://github.com/tphakala/go-remedy)\n\n## Features\n\n- JWT authentication with automatic token management\n- Entry CRUD operations (Create, Read, Update, Delete, Merge)\n- Attachment upload and download\n- Type-safe query builder for AR qualifications\n- Built-in request serialization (avoids BMC Error 9093)\n- Token bucket rate limiting\n- Context-aware with cancellation support\n- Zero external runtime dependencies (stdlib only)\n\n## Installation\n\n```bash\ngo get github.com/tphakala/go-remedy\n```\n\nRequires Go 1.25 or later.\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"log\"\n    \"time\"\n\n    \"github.com/tphakala/go-remedy\"\n)\n\nfunc main() {\n    // Create client with options\n    client := remedy.New(\"https://remedy.example.com:8443\",\n        remedy.WithTimeout(30*time.Second),\n        remedy.WithRateLimit(10), // 10 requests/second\n    )\n    defer client.Close()\n\n    ctx := context.Background()\n\n    // Authenticate\n    if err := client.Login(ctx, \"username\", \"password\"); err != nil {\n        log.Fatal(err)\n    }\n    defer client.Logout(ctx)\n\n    // List entries with filtering\n    entries, err := client.Entries().List(ctx, \"HPD:Help Desk\",\n        remedy.WithQualification(\"'Status' = \\\"Open\\\"\"),\n        remedy.WithFields(\"Request ID\", \"Summary\", \"Status\"),\n        remedy.WithLimit(100),\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    for _, entry := range entries.Entries {\n        log.Printf(\"Request: %s - %s\",\n            entry.Values[\"Request ID\"],\n            entry.Values[\"Summary\"])\n    }\n}\n```\n\n## Usage\n\n### Client Configuration\n\n```go\n// Basic client\nclient := remedy.New(\"https://remedy.example.com:8443\")\n\n// With options\nclient := remedy.New(\"https://remedy.example.com:8443\",\n    remedy.WithHTTPClient(customHTTPClient),    // Custom HTTP client\n    remedy.WithTimeout(60*time.Second),         // Request timeout\n    remedy.WithRateLimit(5),                    // 5 requests/second\n    remedy.WithTokenLifetime(time.Hour),        // JWT token lifetime (default: 1h)\n    remedy.WithRefreshThreshold(5*time.Minute), // Refresh before expiry (default: 5m)\n    remedy.WithAutoRefresh(true),               // Enable auto-refresh (default: true)\n)\n```\n\n### Authentication\n\n```go\n// Standard login\nerr := client.Login(ctx, \"username\", \"password\")\n\n// Login with additional auth string (for servers requiring extra context)\nerr := client.LoginWithAuth(ctx, \"username\", \"password\", \"authString\")\n\n// Check authentication status\nif client.IsAuthenticated() {\n    // ...\n}\n\n// Logout\nerr := client.Logout(ctx)\n```\n\n### Automatic Token Refresh\n\nBMC Remedy JWT tokens expire after 1 hour. The client automatically handles token refresh:\n\n- **Credentials are stored** after `Login()` for automatic re-authentication\n- **Proactive refresh** occurs when the token is within 5 minutes of expiry (configurable)\n- **Concurrent safety** ensures only one refresh occurs even under high load\n\n```go\n// Tokens refresh automatically - no action needed for long-running applications\nclient := remedy.New(\"https://remedy.example.com:8443\")\nclient.Login(ctx, \"user\", \"pass\")\n\n// This works even hours later - token refreshes automatically\nentries, _ := client.Entries().List(ctx, \"HPD:Help Desk\")\n\n// For security-sensitive applications, clear stored credentials when done\nclient.ClearCredentials() // Disables auto-refresh, credentials removed from memory\n\n// Disable auto-refresh entirely if preferred\nclient := remedy.New(\"https://remedy.example.com:8443\",\n    remedy.WithAutoRefresh(false),\n)\n```\n\n### Entry Operations\n\n```go\n// Get single entry\nentry, err := client.Entries().Get(ctx, \"HPD:Help Desk\", \"REQ000001\")\n\n// List entries with options\nentries, err := client.Entries().List(ctx, \"HPD:Help Desk\",\n    remedy.WithQualification(\"'Status' = \\\"Open\\\"\"),\n    remedy.WithFields(\"Request ID\", \"Summary\", \"Status\"),\n    remedy.WithSort(\"Create Date\", remedy.SortDesc),\n    remedy.WithLimit(50),\n    remedy.WithOffset(100),\n)\n\n// Create entry\nentry, err := client.Entries().Create(ctx, \"HPD:Help Desk\", map[string]any{\n    \"Summary\":     \"New ticket summary\",\n    \"Description\": \"Detailed description\",\n    \"Status\":      \"New\",\n})\n\n// Update entry\nerr := client.Entries().Update(ctx, \"HPD:Help Desk\", \"REQ000001\", map[string]any{\n    \"Status\": \"In Progress\",\n})\n\n// Delete entry\nerr := client.Entries().Delete(ctx, \"HPD:Help Desk\", \"REQ000001\")\n\n// Delete with force option\nerr := client.Entries().Delete(ctx, \"HPD:Help Desk\", \"REQ000001\",\n    remedy.DeleteOptionForce)\n\n// Merge entry (create or update based on matching criteria)\nentry, err := client.Entries().Merge(ctx, \"HPD:Help Desk\", map[string]any{\n    \"Summary\": \"Ticket summary\",\n    // Matching fields determine if create or update\n})\n```\n\n### Query Builder\n\nBuild type-safe AR qualification strings:\n\n```go\n// Simple query\nq := remedy.NewQuery().\n    And(\"Status\", \"=\", \"Open\").\n    Build()\n// Result: 'Status' = \"Open\"\n\n// Complex query with multiple conditions\nq := remedy.NewQuery().\n    And(\"Status\", \"=\", \"Open\").\n    And(\"Priority\", \"\u003c\", 3).\n    Or(\"Urgency\", \"=\", \"Critical\").\n    Build()\n// Result: 'Status' = \"Open\" AND 'Priority' \u003c 3 OR 'Urgency' = \"Critical\"\n\n// Raw qualification for complex expressions\nq := remedy.NewQuery().\n    And(\"Status\", \"=\", \"Open\").\n    Raw(\"'Priority' \u003c 3 OR 'Urgency' = \\\"High\\\"\").\n    Build()\n// Result: 'Status' = \"Open\" AND ('Priority' \u003c 3 OR 'Urgency' = \"High\")\n\n// Use with List\nentries, err := client.Entries().List(ctx, \"Form\",\n    remedy.WithQualification(q),\n)\n```\n\nSupported value types:\n- Strings: `\"value\"` -\u003e `\"value\"`\n- Integers: `123` -\u003e `123`\n- Floats: `3.14` -\u003e `3.14`\n- Booleans: `true` -\u003e `1`, `false` -\u003e `0`\n- Nil: `nil` -\u003e `$NULL$`\n\n### Attachments\n\n```go\n// Download attachment\nreader, err := client.Attachments().Get(ctx, \"Form\", \"EntryID\", \"FieldName\")\nif err != nil {\n    log.Fatal(err)\n}\ndefer reader.Close()\n\ndata, err := io.ReadAll(reader)\n\n// Upload attachment\nfile, err := os.Open(\"document.pdf\")\nif err != nil {\n    log.Fatal(err)\n}\ndefer file.Close()\n\nif err := client.Attachments().Upload(ctx, \"Form\", \"EntryID\", \"FieldName\",\n    \"document.pdf\", file); err != nil {\n    log.Fatal(err)\n}\n```\n\n### Error Handling\n\n```go\nentry, err := client.Entries().Get(ctx, \"Form\", \"InvalidID\")\nif err != nil {\n    // Check for specific error types\n    if errors.Is(err, remedy.ErrNotFound) {\n        log.Println(\"Entry not found\")\n    } else if errors.Is(err, remedy.ErrUnauthorized) {\n        log.Println(\"Authentication required\")\n    } else if errors.Is(err, remedy.ErrForbidden) {\n        log.Println(\"Permission denied\")\n    } else if errors.Is(err, remedy.ErrNoCredentials) {\n        log.Println(\"Token expired and no credentials for refresh\")\n    }\n\n    // Get detailed API error\n    var apiErr *remedy.APIError\n    if errors.As(err, \u0026apiErr) {\n        log.Printf(\"API Error %d: %s - %s\",\n            apiErr.MessageNumber,\n            apiErr.MessageText,\n            apiErr.MessageAppendedText)\n    }\n}\n```\n\n## Request Serialization\n\nBMC Remedy enforces per-user session limits. Concurrent requests with the same user account trigger Error 9093: \"User is currently connected from another machine or incompatible session\".\n\nThis library automatically serializes requests to prevent this error. All API calls pass through an internal queue ensuring only one request executes at a time per client.\n\n## Testing\n\nThe library provides interfaces for all services, making it easy to mock in tests:\n\n```go\n// Your code uses interfaces\ntype EntryServicer interface {\n    Get(ctx context.Context, form, entryID string, opts ...QueryOption) (*Entry, error)\n    List(ctx context.Context, form string, opts ...QueryOption) (*EntryList, error)\n    Create(ctx context.Context, form string, values map[string]any) (*Entry, error)\n    Update(ctx context.Context, form, entryID string, values map[string]any) error\n    Delete(ctx context.Context, form, entryID string, opts ...DeleteOption) error\n    Merge(ctx context.Context, form string, values map[string]any) (*Entry, error)\n}\n```\n\nUse [mockery](https://github.com/vektra/mockery) to generate mocks:\n\n```bash\nmockery --all --dir=. --output=mocks --outpkg=mocks\n```\n\n## License\n\nMIT License - see [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftphakala%2Fgo-remedy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftphakala%2Fgo-remedy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftphakala%2Fgo-remedy/lists"}