{"id":37222257,"url":"https://github.com/comnoco/libopenapi","last_synced_at":"2026-01-15T01:30:06.021Z","repository":{"id":65184306,"uuid":"558033152","full_name":"comnoco/libopenapi","owner":"comnoco","description":"libopenapi is an enterprise grade OpenAPI and Swagger library for go. Designed for high performance and comes with unique high-level and low-level API","archived":false,"fork":true,"pushed_at":"2023-01-11T13:00:22.000Z","size":3525,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-22T18:02:46.415Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"pb33f/libopenapi","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/comnoco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"daveshanley"}},"created_at":"2022-10-26T19:14:33.000Z","updated_at":"2023-01-05T19:45:32.000Z","dependencies_parsed_at":"2023-02-09T03:01:07.807Z","dependency_job_id":null,"html_url":"https://github.com/comnoco/libopenapi","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/comnoco/libopenapi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comnoco%2Flibopenapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comnoco%2Flibopenapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comnoco%2Flibopenapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comnoco%2Flibopenapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/comnoco","download_url":"https://codeload.github.com/comnoco/libopenapi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comnoco%2Flibopenapi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28441031,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-15T00:55:22.719Z","status":"ssl_error","status_checked_at":"2026-01-15T00:55:20.945Z","response_time":107,"last_error":"SSL_read: 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-15T01:30:04.959Z","updated_at":"2026-01-15T01:30:05.855Z","avatar_url":"https://github.com/comnoco.png","language":"Go","funding_links":["https://github.com/sponsors/daveshanley"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\t\u003cimg src=\"libopenapi-logo.png\" alt=\"libopenapi\" height=\"300px\" width=\"450px\"/\u003e\n\u003c/p\u003e\n\n# libopenapi - enterprise grade OpenAPI tools for golang.\n\n\n![Pipeline](https://github.com/pb33f/libopenapi/workflows/Build/badge.svg)\n[![GoReportCard](https://goreportcard.com/badge/github.com/pb33f/libopenapi)](https://goreportcard.com/report/github.com/pb33f/libopenapi)\n[![codecov](https://codecov.io/gh/pb33f/libopenapi/branch/main/graph/badge.svg?)](https://codecov.io/gh/pb33f/libopenapi)\n[![discord](https://img.shields.io/discord/923258363540815912)](https://discord.gg/x7VACVuEGP)\n[![Docs](https://img.shields.io/badge/godoc-reference-5fafd7)](https://pkg.go.dev/github.com/pb33f/libopenapi)\n\nlibopenapi has full support for Swagger (OpenAPI 2), OpenAPI 3, and OpenAPI 3.1. It can handle the largest and most\ncomplex specifications you can think of.\n\n---\n\n## Sponsors \u0026 users\nIf your company is using `libopenapi`, please considering [supporting this project](https://github.com/sponsors/daveshanley), \nlike our _very kind_ sponsors:\n\n\u003cp align=\"center\"\u003e\n\t\u003ca href=\"//www.speakeasyapi.dev\"\u003e\u003cimg src=\".github/sponsors/speakeasy.png\" alt=\"Speakeasy\" height=\"100px\"/\u003e\u003c/a\u003e\n    \u003cbr/\u003e\n    \u003ca href=\"//www.speakeasyapi.dev\"\u003eSpeakeasy\u003c/a\u003e\n\u003c/p\u003e\n\n`libopenapi` is pretty new, so our list of notable projects that depend on `libopenapi` is small (let me know if you'd like to add your project)\n\n- [github.com/danielgtaylor/restish](https://github.com/danielgtaylor/restish) - \"Restish is a CLI for interacting with REST-ish HTTP APIs\"\n- [github.com/daveshanley/vacuum](https://github.com/daveshanley/vacuum) - \"The world's fastest and most scalable OpenAPI/Swagger linter/quality tool\"\n\n---\n\n## Come chat with us\n\nNeed help? Have a question? Want to share your work? [Join our discord](https://discord.gg/x7VACVuEGP) and\ncome say hi!\n\n## Contents\n\n- [Installing libopenapi](#installing)\n- [Load an OpenAPI 3.1 or 3.0 specification into a model](#load-an-openapi-spec-into-a-model)\n- [Load a Swagger Spec into a model](#load-a-swagger-spec-into-a-model)\n- [Discover what changed](#discover-what-changed)\n- [Loading complex types using extensions](#loading-extensions-using-complex-types)\n- [Creating an index of an OpenAPI specification](#creating-an-index-of-an-openapi-specification)\n- [Resolving an OpenAPI specification](#resolving-an-openapi-specification)\n- [Checking for circular errors without resolving](#checking-for-circular-errors-without-resolving)\n- [Extracting circular refs and resolving errors when building a document](#extracting-circular-refs-and-resolving-errors-when-building-a-document)\n- [Mutating the model](#mutating-the-model)\n\n\u003e **Read the docs at [https://pkg.go.dev/github.com/pb33f/libopenapi](https://pkg.go.dev/github.com/pb33f/libopenapi)**\n\u003e \n---\n\n## Introduction - Why?\n\nThere is already a really great OpenAPI library for golang, it's called [kin-openapi](https://github.com/getkin/kin-openapi).\n\n### Why does `libopenapi` exist?\n\n[kin-openapi](https://github.com/getkin/kin-openapi) is great, and you should go and use it.\n\n#### If you're still reading, here is why `libopenapi` might be useful.\n\n\u003e  **_kin-openapi missing a few critical features_**... They are so important, this entire toolset was created to address\n\u003e those gaps.\n\nWhen building tooling that needs to analyze OpenAPI specifications at a *low* level, [kin-openapi](https://github.com/getkin/kin-openapi)\n**runs out of power** when you need to know the original line numbers and columns, or comments within all keys and values \nin the specification.\n\nAll that data is **lost** when the OpenAPI specification is loaded in by [kin-openapi](https://github.com/getkin/kin-openapi). \nMainly because the library will unmarshal data **directly into structs**, which works great - if you **_don't_** need \naccess to the original specification low level details.\n\n### Why not just modify kin-openapi?\n\nIt would require a fundamental re-build of the entire library, with a different design to expose the same functionality.\n\n---\n\n## libopenapi retains _everything_.\n\n`libopenapi` has been designed to retain all of that really low-level detail about the AST, line numbers, column numbers, \ncomments, original AST structure - everything you could need.\n\n`libopenapi` has a **porcelain** (high-level) and a **plumbing** (low-level) API. Every high level struct, has the \nability to `GoLow()` and dive from the high-level model, down to the low-level model and look-up any detail about the \nunderlying raw data backing that model.\n\nThis library exists because this very need existed inside [VMware](https://vmware.com). The company built an internal \nversion of `libopenapi`, which isn't something that can be released as it's customized for VMware (and it's incomplete).\n\n`libopenapi` is the result of years of learning and battle testing OpenAPI in golang. This library represents what would\nhave been created, if we knew then - what we know now.\n\n## what-changed: a complete diff engine\n\nCompare Swagger and OpenAPI documents for all changes made across the specification. Every detail is checked across\nall versions of OpenAPI and returns a simple to navigate report of every single change made. \n\nEverything modified, added or removed is reported, complete with original/new values, line numbers and columns. \n\n\u003e Need to know which **line**, or **column** number a key or value for something is? **`libopenapi` has you covered**.\n\n## Comes with an Index and a Resolver\n\nWant a lightning fast way to look up any element in an OpenAPI swagger spec? **`libopenapi` has you covered**.\n\nNeed a way to 'resolve' OpenAPI documents that are exploded out across multiple files, remotely or locally? \n**`libopenapi` has you covered**.\n\n---\n### Quick-start tutorial\n\n👀 **Get rolling fast using `libopenapi` with the \n[Parsing OpenAPI files using go](https://quobix.com/articles/parsing-openapi-using-go/)** guide 👀\n\n\u003e Read the full docs at [https://pkg.go.dev](https://pkg.go.dev/github.com/pb33f/libopenapi)\n\n---\n\n## Installing\nGrab the latest release of **libopenapi**\n\n```\ngo get github.com/pb33f/libopenapi\n```\n\n## Load an OpenAPI spec into a model\n\n```go\n// import the library\nimport \"github.com/pb33f/libopenapi\"\n\nfunc readSpec() {\n    \n    // load an OpenAPI 3 specification from bytes\n    petstore, _ := ioutil.ReadFile(\"test_specs/petstorev3.json\")\n    \n    // create a new document from specification bytes\n    document, err := libopenapi.NewDocument(petstore)\n    \n    // if anything went wrong, an error is thrown\n    if err != nil {\n        panic(fmt.Sprintf(\"cannot create new document: %e\", err))\n    }\n    \n    // because we know this is a v3 spec, we can build a ready to go model from it.\n    v3Model, errors := document.BuildV3Model()\n    \n    // if anything went wrong when building the v3 model, a slice of errors will be returned\n    if len(errors) \u003e 0 {\n        for i := range errors {\n            fmt.Printf(\"error: %e\\n\", errors[i])\n        }\n        panic(fmt.Sprintf(\"cannot create v3 model from document: %d errors reported\", \n            len(errors)))\n    }\n    \n    // get a count of the number of paths and schemas.\n    paths := len(v3Model.Model.Paths.PathItems)\n    schemas := len(v3Model.Model.Components.Schemas)\n    \n    // print the number of paths and schemas in the document\n    fmt.Printf(\"There are %d paths and %d schemas in the document\", paths, schemas)\n}\n```\n\nThis will print: \n\n```\nThere are 13 paths and 8 schemas in the document\n```\n\n\n## Load a Swagger spec into a model\n```go\n// import the library\nimport \"github.com/pb33f/libopenapi\"\n\nfunc readSpec() {\n\n    // load a Swagger specification from bytes\n    petstore, _ := ioutil.ReadFile(\"test_specs/petstorev2.json\")\n    \n    // create a new document from specification bytes\n    document, err := libopenapi.NewDocument(petstore)\n    \n    // if anything went wrong, an error is thrown\n    if err != nil {\n        panic(fmt.Sprintf(\"cannot create new document: %e\", err))\n    }\n    \n    // because we know this is a v2 spec, we can build a ready to go model from it.\n    v2Model, errors := document.BuildV2Model()\n    \n    // if anything went wrong when building the v3 model, a slice of errors will be returned\n    if len(errors) \u003e 0 {\n        for i := range errors {\n            fmt.Printf(\"error: %e\\n\", errors[i])    \n        }\n        panic(fmt.Sprintf(\"cannot create v3 model from document: %d errors reported\", \n            len(errors)))\n    }\n    \n    // get a count of the number of paths and schemas.\n    paths := len(v2Model.Model.Paths.PathItems)\n    schemas := len(v2Model.Model.Definitions.Definitions)\n    \n    // print the number of paths and schemas in the document\n    fmt.Printf(\"There are %d paths and %d schemas in the document\", paths, schemas)\n}\n```\n\nThis will print: \n\n```\nThere are 14 paths and 6 schemas in the document\n```\n\n### Dropping down from the high-level API to the low-level one\n\nThis example shows how after loading an OpenAPI spec into a document, navigating to an Operation is pretty simple. \nIt then shows how to _drop-down_ (using `GoLow())` to the low-level API and query the line and start \ncolumn of the RequestBody description.\n\n```go\n// load an OpenAPI 3 specification from bytes\npetstore, _ := ioutil.ReadFile(\"test_specs/petstorev3.json\")\n\n// create a new document from specification bytes \n// (ignore errors for the same of the example)\ndocument, _ := libopenapi.NewDocument(petstore)\n\n// because we know this is a v3 spec, we can build a ready to go model from it \n// (ignoring errors for the example)\nv3Model, _ := document.BuildV3Model()\n\n// extract the RequestBody from the 'put' operation under the /pet path\nreqBody := document.Paths.PathItems[\"/pet\"].Put.RequestBody\n\n// dropdown to the low-level API for RequestBody\nlowReqBody := reqBody.GoLow() \n\n// print out the value, the line it appears on and the \n// start columns for the key and value of the nodes.\nfmt.Printf(\"value is %s, the value is on line %d, \" + \n    \"starting column %d, the key is on line %d, column %d\", \n    reqBody.Description, \n    lowReqBody.Description.ValueNode.Line, \n    lowReqBody.Description.ValueNode.Column,\n    lowReqBody.Description.KeyNode.Line, \n    lowReqBody.KeyNode.Column)\n```\n---\n\n## Discover what changed\n\nlibopenapi comes with a complete **diff engine**\n\nWant to know what changed between two different OpenAPI or Swagger specifications? libopenapi makes this super, super easy.\n\n```go\n// How to compare two different OpenAPI specifications for changes between them.\n\n// load an original OpenAPI 3 specification from bytes\nburgerShopOriginal, _ := ioutil.ReadFile(\"test_specs/burgershop.openapi.yaml\")\n\n// load an **updated** OpenAPI 3 specification from bytes\nburgerShopUpdated, _ := ioutil.ReadFile(\"test_specs/burgershop.openapi-modified.yaml\")\n\n// create a new document from original specification bytes\noriginalDoc, err := NewDocument(burgerShopOriginal)\n\n// if anything went wrong, an error is thrown\nif err != nil {\n    panic(fmt.Sprintf(\"cannot create new document: %e\", err))\n}\n\n// create a new document from updated specification bytes\nupdatedDoc, err := NewDocument(burgerShopUpdated)\n\n// if anything went wrong, an error is thrown\nif err != nil {\n    panic(fmt.Sprintf(\"cannot create new document: %e\", err))\n}\n\n// Compare documents for all changes made\ndocumentChanges, errs := CompareDocuments(originalDoc, updatedDoc)\n    \n// If anything went wrong when building models for documents.\nif len(errs) \u003e 0 {\n    for i := range errs {\n        fmt.Printf(\"error: %e\\n\", errs[i])\n    }\n    panic(fmt.Sprintf(\"cannot compare documents: %d errors reported\", len(errs)))\n}\n\n// Extract SchemaChanges from components changes.\nschemaChanges := documentChanges.ComponentsChanges.SchemaChanges\n\n// Print out some interesting stats about the OpenAPI document changes.\nfmt.Printf(\"There are %d changes, of which %d are breaking. %v schemas have changes.\",\n    documentChanges.TotalChanges(), documentChanges.TotalBreakingChanges(), len(schemaChanges))\n\n```\n\nEvery change can be explored and navigated just like you would use the high or low level models.\n\n---\n\n## Loading extensions using complex types\n\nIf you're using extensions with complex types (rather that just simple strings and primitives), then there is a good\nchance you're going to want some simple way to marshal extensions into those structs.\n\nSince version v0.3.2 There is a new method to make this simple available in the `high` package within the `datamodel` \npackage. It's called `UnpackExtensions`\n\nIt's a generic function that requires the custom type and the **low** model type of the object that contains the extensions\nyou'd like to unpack.\n\nHere is an example of complex types being extracted easily from OpenAPI extensions.\n\n```go\n\nimport (\n    \"github.com/pb33f/libopenapi\"\n    high \"github.com/pb33f/libopenapi/datamodel/high\"\n    lowbase \"github.com/pb33f/libopenapi/datamodel/low/base\"\n    lowv3 \"github.com/pb33f/libopenapi/datamodel/low/v3\"\n)\n\n// define an example struct representing a cake\n// super important to remember to use hints/meta-data to map properties correctly.\ntype cake struct {\n    Candles               int    `yaml:\"candles\"`\n    Frosting              string `yaml:\"frosting\"`\n    Some_Strange_Var_Name string `yaml:\"someStrangeVarName\"`\n}\n\n// define a struct that holds a map of cake pointers.\ntype cakes struct {\n    Description string\n    Cakes       map[string]*cake\n}\n\n// define a struct representing a burger\ntype burger struct {\n    Sauce string\n    Patty string\n}\n\n// define a struct that holds a map of cake pointers\ntype burgers struct {\n    Description string\n    Burgers     map[string]*burger\n}\n\nfunc main() {\n\n    // create a specification with a schema and parameter that use complex \n    // custom cakes and burgers extensions.\n    spec := `openapi: \"3.1\"\n    components:\n    schemas:\n    SchemaOne:\n      description: \"Some schema with custom complex extensions\"\n      x-custom-cakes:\n        description: some cakes\n        cakes:\n          someCake:\n            candles: 10\n            frosting: blue\n            someStrangeVarName: mapping is required to extract these.\n          anotherCake:\n            candles: 1\n            frosting: green\n    parameters:\n    ParameterOne:\n      description: \"Some parameter also using complex extensions\"\n      x-custom-burgers:\n        description: some burgers\n        burgers:\n          someBurger:\n            sauce: ketchup\n            patty: meat \n          anotherBurger:\n            sauce: mayo\n            patty: lamb`\n    \n    // create a new document from specification bytes\n    doc, err := libopenapi.NewDocument([]byte(spec))\n    \n    // if anything went wrong, an error is thrown\n    if err != nil {\n        panic(fmt.Sprintf(\"cannot create new document: %e\", err))\n    }\n    \n    // build a v3 model.\n    docModel, errs := doc.BuildV3Model()\n    \n    // if anything went wrong building, indexing and resolving the model, errors are thrown.\n    if errs != nil {\n        panic(fmt.Sprintf(\"cannot create new document: %e\", errs))\n    }\n    \n    // get a reference to SchemaOne and ParameterOne\n    schemaOne := docModel.Model.Components.Schemas[\"SchemaOne\"].Schema()\n    parameterOne := docModel.Model.Components.Parameters[\"ParameterOne\"]\n    \n    // unpack schemaOne extensions into complex `cakes` type\n    schemaOneExtensions, schemaUnpackErrors := high.UnpackExtensions[cakes, *lowbase.Schema](schemaOne)\n    if schemaUnpackErrors != nil {\n        panic(fmt.Sprintf(\"cannot unpack schema extensions: %e\", err))\n    }\n    \n    // unpack parameterOne into complex `burgers` type\n    parameterOneExtensions, paramUnpackErrors := high.UnpackExtensions[burgers, *lowv3.Parameter](parameterOne)\n    if paramUnpackErrors != nil {\n        panic(fmt.Sprintf(\"cannot unpack parameter extensions: %e\", err))\n    }\n    \n    // extract extension by name for schemaOne\n    customCakes := schemaOneExtensions[\"x-custom-cakes\"]\n    \n    // extract extension by name for schemaOne\n    customBurgers := parameterOneExtensions[\"x-custom-burgers\"]\n    \n    // print out schemaOne complex extension details.\n    fmt.Printf(\"schemaOne 'x-custom-cakes' (%s) has %d cakes, 'someCake' has %d candles and %s frosting\\n\",\n        customCakes.Description,\n        len(customCakes.Cakes),\n        customCakes.Cakes[\"someCake\"].Candles,\n        customCakes.Cakes[\"someCake\"].Frosting,\n    )\n    \n    // print out parameterOne complex extension details.\n    fmt.Printf(\"parameterOne 'x-custom-burgers' (%s) has %d burgers, 'anotherBurger' has %s sauce and a %s patty\\n\",\n        customBurgers.Description,\n        len(customBurgers.Burgers),\n        customBurgers.Burgers[\"anotherBurger\"].Sauce,\n        customBurgers.Burgers[\"anotherBurger\"].Patty,\n    )\n}\n```\n\nThis will output:\n\n```text\nschemaOne 'x-custom-cakes' (some cakes) has 2 cakes, 'someCake' has 10 candles and blue frosting\nparameterOne 'x-custom-burgers' (some burgers) has 2 burgers, 'anotherBurger' has mayo sauce and a lamb patty\n```\n---\n\n## Creating an index of an OpenAPI Specification\n\nAn index is really useful when a map of an OpenAPI spec is needed. Knowing where all the references are and where\nthey point, is very useful when resolving specifications, or just looking things up. \n\n### Creating an index from the Stripe OpenAPI Spec\n\n```go\n// define a rootNode to hold our raw stripe spec AST.\nvar rootNode yaml.Node\n\n// load in the stripe OpenAPI specification into bytes (it's pretty meaty)\nstripeSpec, _ := ioutil.ReadFile(\"test_specs/stripe.yaml\")\n\n// unmarshal spec into our rootNode\nyaml.Unmarshal(stripeSpec, \u0026rootNode)\n\n// create a new specification index.\nindex := index.NewSpecIndex(\u0026rootNode)\n\n// print out some statistics\nfmt.Printf(\"There are %d references\\n\"+\n    \"%d paths\\n\"+\n    \"%d operations\\n\"+\n    \"%d schemas\\n\"+\n    \"%d enums\\n\"+\n    \"%d polymorphic references\",\n    len(index.GetAllCombinedReferences()),\n    len(index.GetAllPaths()),\n    index.GetOperationCount(),\n    len(index.GetAllSchemas()),\n    len(index.GetAllEnums()),\n    len(index.GetPolyOneOfReferences())+len(index.GetPolyAnyOfReferences()))\n```\n\n## Resolving an OpenAPI Specification\n\nWhen creating an index, the raw AST that uses [yaml.Node](https://pkg.go.dev/gopkg.in/yaml.v3#Node) is preserved \nwhen looking up local, file-based and remote references. This means that if required, the spec can be 'resolved' \nand all the reference nodes will be replaced with the actual data they reference.\n\nWhat this looks like from a spec perspective.\n\nIf the specification looks like this:\n\n```yaml\npaths:\n  \"/some/path/to/a/thing\":\n    get:\n      responses:\n        \"200\":\n          $ref: '#/components/schemas/MySchema'\ncomponents:\n  schemas:\n   MySchema:\n     type: string\n     description: This is my schema that is great!\n```\n\nWill become this (as represented by the root [yaml.Node](https://pkg.go.dev/gopkg.in/yaml.v3#Node)\n\n```yaml\npaths:\n  \"/some/path/to/a/thing\":\n    get:\n      responses:\n        \"200\":\n          type: string\n          description: This is my schema that is great!\ncomponents:\n  schemas:\n   MySchema:\n     type: string\n     description: This is my schema that is great!\n```\n\u003e This is not a valid spec, it's just design to illustrate how resolving works.\n\nThe reference has been 'resolved', so when reading the raw AST, there is no lookup required anymore.\n\n### Resolving Example:\n\nUsing the Stripe API as an example, we can resolve all references, and then count how many circular reference issues\nwere found.\n\n```go\nimport (\n    \"github.com/pb33f/libopenapi/index\"\n    \"github.com/pb33f/libopenapi/resolver\"\n)\n\n// create a yaml.Node reference as a root node.\nvar rootNode yaml.Node\n\n//  load in the Stripe OpenAPI spec (lots of polymorphic complexity in here)\nstripeBytes, _ := ioutil.ReadFile(\"../test_specs/stripe.yaml\")\n\n// unmarshal bytes into our rootNode.\n_ = yaml.Unmarshal(stripeBytes, \u0026rootNode)\n\n// create a new spec index (resolver depends on it)\nindex := index.NewSpecIndex(\u0026rootNode)\n\n// create a new resolver using the index.\nresolver := resolver.NewResolver(index)\n\n// resolve the document, if there are circular reference errors, they are returned/\n// WARNING: this is a destructive action and the rootNode will be \n// PERMANENTLY altered and cannot be unresolved\ncircularErrors := resolver.Resolve()\n\n// The Stripe API has a bunch of circular reference problems, \n// mainly from polymorphism. So let's print them out.\nfmt.Printf(\"There are %d circular reference errors, \" +\n    \"%d of them are polymorphic errors, %d are not\",\n    len(circularErrors), \n    len(resolver.GetPolymorphicCircularErrors()), \n    len(resolver.GetNonPolymorphicCircularErrors()))\n```\n\nThis will output: \n\n`There are 21 circular reference errors, 19 of them are polymorphic errors, 2 are not`\n\n\u003e Important to remember: Resolving is **destructive** and will permanently change the tree, it cannot be un-resolved.\n\n### Checking for circular errors without resolving\n\nResolving is destructive, the original reference nodes are gone and all replaced, so how do we check for circular references\nin a non-destructive way? Instead of calling `Resolve()`, we can call `CheckForCircularReferences()` instead.\n\nThe same code as `Resolve()` executes, except the tree is **not actually resolved**, _nothing_ changes and _no destruction_\noccurs. A handy way to perform circular reference analysis on the specification, without permanently altering it.\n\n```go\nimport (\n    \"github.com/pb33f/libopenapi/index\" \n    \"github.com/pb33f/libopenapi/resolver\"\n)\n\n// create a yaml.Node reference as a root node.\nvar rootNode yaml.Node\n\n//  load in the Stripe OpenAPI spec (lots of polymorphic complexity in here)\nstripeBytes, _ := ioutil.ReadFile(\"../test_specs/stripe.yaml\")\n\n// unmarshal bytes into our rootNode.\n_ = yaml.Unmarshal(stripeBytes, \u0026rootNode)\n\n// create a new spec index (resolver depends on it)\nindex := index.NewSpecIndex(\u0026rootNode)\n\n// create a new resolver using the index.\nresolver := resolver.NewResolver(index)\n\n// extract circular reference errors without any changes to the original tree.\ncircularErrors := resolver.CheckForCircularReferences()\n\n// The Stripe API has a bunch of circular reference problems, \n// mainly from polymorphism. So let's print them out.\nfmt.Printf(\"There are %d circular reference errors, \" +\n    \"%d of them are polymorphic errors, %d are not\",\n    len(circularErrors),\n    len(resolver.GetPolymorphicCircularErrors()),\n    len(resolver.GetNonPolymorphicCircularErrors()))\n```\n\n### Extracting circular refs and resolving errors when building a document\n\nTo avoid having to create an index and a resolver each time you want to both create\na document and resolve it / check for errors, don't worry, circular references are checked\nautomatically and are available in the returned `[]errors` which building a document.\n\nThe errors returned by the slice are pointers to `*resolver.ResolvingError` which contains\nrich details about the issue, where it was found and the journey it took to get there.\n\nHere is an example:\n\n```go\n// create a specification with an obvious and deliberate circular reference \nspec := `\nopenapi: \"3.1\"\ncomponents:\n  schemas:\n    One:\n      description: \"test one\"\n      properties:\n        things:\n          \"$ref\": \"#/components/schemas/Two\"\n    Two:\n      description: \"test two\"\n      properties:\n        testThing:\n          \"$ref\": \"#/components/schemas/One\"\n`\n// create a new document from specification bytes\ndoc, err := NewDocument([]byte(spec))\n\n// if anything went wrong, an error is thrown\nif err != nil {\n    panic(fmt.Sprintf(\"cannot create new document: %e\", err))\n}\n_, errs := doc.BuildV3Model()\n\n// extract resolving error\nresolvingError := errs[0]\n\n// resolving error is a pointer to *resolver.ResolvingError\n// which provides access to rich details about the error.\ncircularReference := resolvingError.(*resolver.ResolvingError).CircularReference\n\n// capture the journey with all details\nvar buf strings.Builder\nfor n := range circularReference.Journey {\n\n    // add the full definition name to the journey.\n    buf.WriteString(circularReference.Journey[n].Definition)\n    if n \u003c len(circularReference.Journey)-1 {\n        buf.WriteString(\" -\u003e \")\n    }\n}\n\n// print out the journey and the loop point.\nfmt.Printf(\"Journey: %s\\n\", buf.String())\nfmt.Printf(\"Loop Point: %s\", circularReference.LoopPoint.Definition)\n```\n\nWill output:\n\n```text\nJourney: #/components/schemas/Two -\u003e #/components/schemas/One -\u003e #/components/schemas/Two\nLoop Point: #/components/schemas/Two\n```\n\n---\n\n\n## Mutating the model\n\nHaving a read-only model is great, but what about when we want to modify the model and mutate values, or even add new\ncontent to the model? What if we also want to save that output as an updated specification - but we don't want to\njumble up the original ordering of the source.\n\n### marshaling and unmarshalling to and from structs into JSON/YAML is not ideal.\n\nWhen we straight up use `json.Marshal` or `yaml.Marshal` to send structs to be rendered into the desired format, there\nis no guarantee as to the order in which each component will be rendered. This works great if...\n\n- We don't care about the spec being randomly ordered.\n- We don't care about code-reviews.\n- We don't actually care about this very much.\n\n### But if we do care...\n\nThen libopenpi provides a way to mutate the model, that keeps the original [yaml.Node API](https://pkg.go.dev/gopkg.in/yaml.v3#Node)\ntree in-tact. It allows us to make changes to values in place, and serialize back to JSON or YAML without any changes to\nother content order.\n\n```go\n// create very small, and useless spec that does nothing useful, except showcase this feature.\nspec := `\nopenapi: 3.1.0\ninfo:\n  title: This is a title\n  contact:\n    name: Some Person\n    email: some@emailaddress.com\n  license:\n    url: http://some-place-on-the-internet.com/license\n`\n// create a new document from specification bytes\ndocument, err := libopenapi.NewDocument([]byte(spec))\n\n// if anything went wrong, an error is thrown\nif err != nil {\n    panic(fmt.Sprintf(\"cannot create new document: %e\", err))\n}\n\n// because we know this is a v3 spec, we can build a ready to go model from it.\nv3Model, errors := document.BuildV3Model()\n\n// if anything went wrong when building the v3 model, a slice of errors will be returned\nif len(errors) \u003e 0 {\n    for i := range errors {\n        fmt.Printf(\"error: %e\\n\", errors[i])\n    }\n    panic(fmt.Sprintf(\"cannot create v3 model from document: %d errors reported\", len(errors)))\n}\n\n// mutate the title, to do this we currently need to drop down to the low-level API.\nv3Model.Model.GoLow().Info.Value.Title.Mutate(\"A new title for a useless spec\")\n\n// mutate the email address in the contact object.\nv3Model.Model.GoLow().Info.Value.Contact.Value.Email.Mutate(\"buckaroo@pb33f.io\")\n\n// mutate the name in the contact object.\nv3Model.Model.GoLow().Info.Value.Contact.Value.Name.Mutate(\"Buckaroo\")\n\n// mutate the URL for the license object.\nv3Model.Model.GoLow().Info.Value.License.Value.URL.Mutate(\"https://pb33f.io/license\")\n\n// serialize the document back into the original YAML or JSON\nmutatedSpec, serialError := document.Serialize()\n\n// if something went wrong serializing\nif serialError != nil {\n    panic(fmt.Sprintf(\"cannot serialize document: %e\", serialError))\n}\n\n// print our modified spec!\nfmt.Println(string(mutatedSpec))\n```\n\nWhich will output:\n\n```yaml\nopenapi: 3.1.0\ninfo:\n    title: A new title for a useless spec\n    contact:\n         name: Buckaroo\n         email: buckaroo@pb33f.io\n    license:\n         url: https://pb33f.io/license\n\n```\n\u003e It's worth noting that the original line numbers and column numbers **won't be respected** when calling `Serialize()`,\n\u003e A new `Document` needs to be created from that raw YAML to continue processing after serialization.\n\n\u003e **Read the full docs at [https://pkg.go.dev](https://pkg.go.dev/github.com/pb33f/libopenapi)**\n\n---\n\nThe library heavily depends on the fantastic (yet hard to get used to) [yaml.Node API](https://pkg.go.dev/gopkg.in/yaml.v3#Node).\nThis is what is exposed by the `GoLow` API. \n\n\u003e It does not matter if the input material is JSON or YAML, the [yaml.Node API](https://pkg.go.dev/gopkg.in/yaml.v3#Node) supports both and \n\u003e creates a great way to navigate the AST of the document.\n\nLogo gopher is modified, originally from [egonelbre](https://github.com/egonelbre/gophers)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcomnoco%2Flibopenapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcomnoco%2Flibopenapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcomnoco%2Flibopenapi/lists"}