{"id":37148235,"url":"https://github.com/cavaliercoder/go-aggregated-writer","last_synced_at":"2026-01-14T17:29:48.522Z","repository":{"id":79498643,"uuid":"386271975","full_name":"cavaliercoder/go-aggregated-writer","owner":"cavaliercoder","description":null,"archived":true,"fork":false,"pushed_at":"2021-07-15T11:56:19.000Z","size":3,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-06-21T18:11:03.118Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cavaliercoder.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2021-07-15T11:54:32.000Z","updated_at":"2023-01-28T06:14:50.000Z","dependencies_parsed_at":"2023-04-07T04:35:01.597Z","dependency_job_id":null,"html_url":"https://github.com/cavaliercoder/go-aggregated-writer","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cavaliercoder/go-aggregated-writer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cavaliercoder%2Fgo-aggregated-writer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cavaliercoder%2Fgo-aggregated-writer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cavaliercoder%2Fgo-aggregated-writer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cavaliercoder%2Fgo-aggregated-writer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cavaliercoder","download_url":"https://codeload.github.com/cavaliercoder/go-aggregated-writer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cavaliercoder%2Fgo-aggregated-writer/sbom","scorecard":{"id":268147,"data":{"date":"2025-08-11","repo":{"name":"github.com/cavaliercoder/go-aggregated-writer","commit":"35d9aca7d136fb850cd474fde25d1ffd98c3c44d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.6,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Code-Review","score":0,"reason":"Found 0/2 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"project is archived","details":["Warn: Repository is archived."],"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T12:41:54.235Z","repository_id":79498643,"created_at":"2025-08-17T12:41:54.235Z","updated_at":"2025-08-17T12:41:54.235Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28428046,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T16:38:47.836Z","status":"ssl_error","status_checked_at":"2026-01-14T16:34:59.695Z","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":[],"created_at":"2026-01-14T17:29:47.906Z","updated_at":"2026-01-14T17:29:48.517Z","avatar_url":"https://github.com/cavaliercoder.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# One less `if err != nil { return err }`\n\nGo is famous for its verbose error handling (only because it coerces you to\nhandle every case) but there are some patterns to keep it under control.\n\nThis isn't meant to be a _Ten Commandments of Error Handling_ or even a good\nidea; it's just a pattern than has been useful to me.\n\nA common scenario for poor error handling in practice is when using `io`\noperations. Specifically, _writes_ typically blow out the length of your method\nwith repetitive error checking or they simply go without error handling at all.\n\nWe've all see this code:\n\n```go\npackage main\n\nimport \"fmt\"\n\nfunc main() {\n    fmt.Println(\"hello world\")\n}\n```\n\nWhat's the one thing missing? Error handling. Yes, I concede, it's not\nexactly necessary when writing to `os.Stdout` (this is what happens under the\nhood in `fmt.Println`). What if we're writing to a file and we do need to make\nsure it succeeds?\n\nEasy. Let's check for errors when we write to the file:\n\n```go\npackage main\n\nimport \"fmt\"\n\nfunc main() {\n    // create a file and check for errors\n    f, err := os.Create(\"./helloWorld.txt\")\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer f.Close()\n\n    // write to file and check for errors\n    _, err = fmt.Fprintln(\"hello world\")\n    if err != nil {\n        log.Fatal(err)\n    }\n}\n```\n\nCool beans. What if the content we're writing is a little more sophisticated and\nrequires multiple successive writes? Maybe we're implementing our own JSON\nstringifier.\n\nLet's implement a naive stringifier that takes a list of strings and prints it\nto a writer as a JSON array.\n\n```go\nfunc Stringify(w io.Writer, a []string) (n int, err error) {\n    w.Write([]byte{'['})\n    for i := 0; i \u003c len(a); i++ {\n        if i \u003e 0 {\n            fmt.Fprint(w, \", \")\n        }\n        fmt.Fprintf(w, `\"%s\"`, a[i])\n    }\n    w.Write([]byte{']'})\n    return\n}\n```\n\nNow, we get a lovely JSON array:\n\n```go\nfunc main() {\n    Stringify(os.Stdout, []string{\"hello\", \"world\"})\n}\n```\n\n```json\n[\"hello\", \"world\"]\n```\n\nThis will work. But `Stringify` always returns `0, nil`, no matter what\nhappens. We need some bookkeeping around each write: \n\n```go\nfunc Stringify(w io.Writer, a []string) (n int, err error) {\n    var nn int\n\n    // write opening bracket\n    nn, err = w.Write([]byte{'['})\n    if err != nil {\n        return\n    }\n    n += int64(nn)\n\n    for i := 0; i \u003c len(a); i++ {\n        if i \u003e 0 {\n            // write separator\n            nn, err = fmt.Fprint(w, \", \")\n            if err != nil {\n                return\n            }\n            n += int64(nn)\n        }\n\n        // write quoted member\n        nn, err = fmt.Fprintf(w, `\"%s\"`, a[i])\n        if err != nil {\n            return\n        }\n        n += int64(nn)\n    }\n\n    // write closing bracket\n    nn, err = w.Write([]byte{']'})\n    if err != nil {\n        return\n    }\n    n += int64(nn)\n\n    return\n}\n```\n\nOof, that's verbose! We're only doing four writes. You can see why so many\ngophers might opt for ignoring the occasional cheaky error.\n\nThere is a more elegant way to solve this that leverages the power of Go's\n`io.Writer` interface. Let's create a middleware writer that wraps our output\nwriter, keeps track of the number of bytes written and handles any errors along\nthe way.\n\nBecause naming is hard, we'll call it the `AggregatedWriter`:\n\n```go\ntype AggregatedWriter struct {\n    w   io.Writer // underlying writer\n    n   int64     // cumulative total of bytes written to w\n    err error     // any error returned by w\n}\n\nfunc NewAggregatedWriter(w io.Writer) *AggregatedWriter {\n    if ag, ok := w.(*AggregatedWriter); ok {\n        return ag // w was already an AggregatedWriter\n    }\n    return \u0026AggregatedWriter{w: w}\n}\n\nfunc (w *AggregatedWriter) Write(p []byte) (n int, err error) {\n    if w.err != nil {\n        return 0, w.err\n    }\n    n, err = w.w.Write(p)\n    w.n += int64(n)\n    w.err = err\n    return\n}\n\nfunc (w *AggregatedWriter) N() int64                     { return w.n }\nfunc (w *AggregatedWriter) Err() error                   { return w.err }\nfunc (w *AggregatedWriter) Result() (n int64, err error) { return w.n, w.err }\n```\n\nThe implementation is very simple but we hopefully get a lot of miles out of it.\n\nThe `AggregatedWriter` implements `io.Writer` so we can use it anywhere you\nwrite to a file or buffer. Let's use it in our `Stringify` method and then talk\nabout how it works.\n\n```go\nfunc Stringify(w io.Writer, a []string) (n int, err error) {\n    w = NewAggregatedWriter(w)\n    w.Write([]byte{'['})\n    for i := 0; i \u003c len(a); i++ {\n        if i \u003e 0 {\n            fmt.Fprint(w, \", \")\n        }\n        fmt.Fprintf(w, `\"%s\"`, a[i])\n    }\n    w.Write([]byte{']'})\n    return w.(*AggregatedWriter).Result()\n}\n```\n\nIt's not much longer that our first example, only this time the caller gets a\nvalid byte count and error result!\n\nLooking into our `Stringify` method, it first wraps the given writer in an\n`AggregatedWriter`, performs writes as before (with no error checking) and then\nreturns a result from the `AggregatedWriter` which will be the cumulative byte\ncount of all writes and any errors that occured writing to `os.Stdout`.\n\nLet's take a closer look at the `AggregatedWriter`:\n\n```go\n// Write implements the io.Writer interface.\nfunc (w *AggregatedWriter) Write(p []byte) (n int, err error) {\n    // check if an error occurred previously - if so, abort this write and\n    // return the previous error\n    if w.err != nil {\n        return 0, w.err\n    }\n\n    // write to the underlying writer and copy the bytes written and any\n    // error to our return values\n    n, err = w.w.Write(p)\n\n    // increment the total written across all calls to this method\n    w.n += int64(n)\n\n    // store any error\n    w.err = err\n\n    // return (n, err) declared in the func signature\n    return\n}\n```\n\nThere is a trade-off to this approach that you may need to pay attention to: \nexecution is not interrupted when a write fails. The right way to use this is to\ndo all your writes, check that they worked, then do stuff that depends on it. If\nthat's not feasible, this approach might not be the right choice.\n\nI hope this was at least an interesting exploration into one pattern for terse\nhandling of similar errors and for stacking `io.Writer` interfaces on top of\neach other.\n\nGive your dog a pat for me.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcavaliercoder%2Fgo-aggregated-writer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcavaliercoder%2Fgo-aggregated-writer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcavaliercoder%2Fgo-aggregated-writer/lists"}