{"id":17045158,"url":"https://github.com/sjkaliski/pinned","last_synced_at":"2026-03-16T06:08:14.667Z","repository":{"id":29388947,"uuid":"120683667","full_name":"sjkaliski/pinned","owner":"sjkaliski","description":"📌 Date based versioning system for Go APIs.","archived":false,"fork":false,"pushed_at":"2022-01-02T02:15:27.000Z","size":2207,"stargazers_count":86,"open_issues_count":2,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-10-25T13:26:08.905Z","etag":null,"topics":["api","go","golang","versioning","versioning-semantics"],"latest_commit_sha":null,"homepage":"","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/sjkaliski.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}},"created_at":"2018-02-07T23:11:46.000Z","updated_at":"2025-06-24T17:41:10.000Z","dependencies_parsed_at":"2022-08-07T14:16:19.561Z","dependency_job_id":null,"html_url":"https://github.com/sjkaliski/pinned","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/sjkaliski/pinned","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjkaliski%2Fpinned","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjkaliski%2Fpinned/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjkaliski%2Fpinned/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjkaliski%2Fpinned/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sjkaliski","download_url":"https://codeload.github.com/sjkaliski/pinned/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjkaliski%2Fpinned/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30570229,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-16T06:02:37.763Z","status":"ssl_error","status_checked_at":"2026-03-16T06:02:14.913Z","response_time":96,"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","go","golang","versioning","versioning-semantics"],"created_at":"2024-10-14T09:36:35.737Z","updated_at":"2026-03-16T06:08:14.646Z","avatar_url":"https://github.com/sjkaliski.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pinned\n\nThis is a proof-of-concept, date based versioning system for APIs inspired by [Stripe's API versioning](https://stripe.com/blog/api-versioning).\n\n![](https://github.com/sjkaliski/pinned/workflows/Go/badge.svg)\n[![GoDoc](https://godoc.org/github.com/sjkaliski/pinned?status.svg)](https://godoc.org/github.com/sjkaliski/pinned)\n[![Go Report Card](https://goreportcard.com/badge/github.com/sjkaliski/pinned)](https://goreportcard.com/report/github.com/sjkaliski/pinned)\n[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/pinned)\n\n## Overview\n\nThis package enables reverse compatibility for a Go API by defining versions and their associated changes. Consequently, API versions can be maintained for long periods of time with minimal effort.\n\n## Usage\n\nSee the included [example](/example) project for detailed usage.\n\nVersioning is done at a resource/struct level. If a type implements `Versionable` it can take advantage of this package.\n\n1. To start, create a new `VersionManager`.\n\n```go\nvm := \u0026pinned.VersionManager{\n  Layout: \"2006-01-02\",\n  Header: \"API Version\",\n}\n```\n\n2. Then add `Versions`.\n\n```go\n// Initial version.\nvm.Add(\u0026pinned.Version{\n  Date: \"2018-02-10\",\n})\n\n// New version.\nvm.Add(\u0026pinned.Version{\n  Date: \"2018-02-11\",\n  Changes: []*pinned.Change{\n    \u0026pinned.Change{\n      Description: \"New things\",\n      Actions: map[string]pinned.Action{\n        \"Object\": someMethod,\n      }\n    }\n  }\n})\n```\n\n`someMethod` is applied to all `type Object`, and has the signature `func(map[string]interface{}) map[string]interface{}`. \n\n3. Handle an incoming request.\n\n```go\nfunc handler(w http.ResponseWriter, r *http.Request) {\n  // Get version from request.\n  v, _ := vm.Parse(r)\n\n  // Set version in context.\n  ctx = pinned.NewContext(r.Context(), v)\n  \n  // ...Fetch resources...\n\n  // Apply version changes to resources.\n  body, _ := vm.Apply(v, data)\n\n  // Write response.\n  data, err := json.Marshal(body)\n  if err != nil {\n    panic(err)\n  }\n\n  w.Header().Set(\"Content-Type\", \"application/json\")\n  w.Write(data)\n}\n```\n\n## Example\n\nConsider the following simple example. An API has a User struct which looks like this:\n\n```go\ntype User struct {\n  ID       uint64\n  FullName string\n}\n```\n\nNow we decide we want to rename `FullName` to `Name`. However, this is a breaking change. To ensure stability, prior to change we set a version `2018-02-10`.\n\nAfter the change, we set a version `2018-02-11`. This version has a change associated with it. This `Change` has an `Action` to be taken on the `User` resource.\n\nThis `Action` is a `func` that _reverses the change_ made in the new version.\n\n```go\nfunc userNameFieldChange(mapping map[string]interface{}) map[string]interface{} {\n  mapping[\"full_name\"] = mapping[\"name\"]\n  delete(mapping, \"name\")\n  return mapping\n}\n```\n\nThere are now two versions, `2018-02-11` and `2018-02-10`. To support the client that requested version `2018-02-10`, the \"changes\" made in version `2018-02-11` are undone, and the User resource now reflects the requested version.\n\nAs versions are added, these changes are sequentially undone. This enables a version to be supported for a long period of time, and allows the developer to focus on new feature development without much concern towards legacy versions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjkaliski%2Fpinned","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsjkaliski%2Fpinned","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjkaliski%2Fpinned/lists"}