Added new Render method to top level document.

Like `RenderAndReload` this method will render / print out the document, however it won’t read it back in and reload the model from the ground up. This is a non-destructive method that does not reload the model from the rendered document.

If you don’t use the low model or you don’t care that the high level model and low-level models are out of sync? No problem. Otherwise keep this in mind that the low level model will only represent the original un-mutated document, as will the index.

Signed-off-by: quobix <dave@quobix.com>
This commit is contained in:
quobix
2023-09-13 07:06:15 -04:00
parent c700c19849
commit 43bf162e0c
6 changed files with 798 additions and 674 deletions

View File

@@ -72,6 +72,21 @@ type Document interface {
// it's too old, so it should be motivation to upgrade to OpenAPI 3. // it's too old, so it should be motivation to upgrade to OpenAPI 3.
RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], []error) RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], []error)
// Render will render the high level model as it currently exists (including any mutations, additions
// and removals to and from any object in the tree). Unlike RenderAndReload, Render will simply print the state
// of the model as it currently exists, and will not re-load the model into memory. It means that the low-level and
// the high-level models will be out of sync, and the index will only be useful for the original document.
//
// Why use this instead of RenderAndReload?
//
// The simple answer is that RenderAndReload is a destructive operation, and will re-build the entire model from
// scratch using the new bytes, which is desirable if you want to make changes to the high level model and then
// 'reload' the model into memory, so that line numbers and column numbers are correct and the index is accurate.
// However, if you don't care about the low-level model, and you're not using the index, and you just want to
// print the state of the model as it currently exists, then Render() is the method to use.
// **IMPORTANT** This method only supports OpenAPI Documents.
Render() ([]byte, error)
// Serialize will re-render a Document back into a []byte slice. If any modifications have been made to the // Serialize will re-render a Document back into a []byte slice. If any modifications have been made to the
// underlying data model using low level APIs, then those changes will be reflected in the serialized output. // underlying data model using low level APIs, then those changes will be reflected in the serialized output.
// //
@@ -168,14 +183,32 @@ func (d *document) Serialize() ([]byte, error) {
} }
func (d *document) RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], []error) { func (d *document) RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], []error) {
newBytes, rerr := d.Render()
if rerr != nil {
return nil, nil, nil, []error{rerr}
}
newDoc, err := NewDocumentWithConfiguration(newBytes, d.config)
if err != nil {
return newBytes, newDoc, nil, []error{err}
}
// build the model.
model, errs := newDoc.BuildV3Model()
if errs != nil {
return newBytes, newDoc, model, errs
}
// this document is now dead, long live the new document!
return newBytes, newDoc, model, nil
}
func (d *document) Render() ([]byte, error) {
if d.highSwaggerModel != nil && d.highOpenAPI3Model == nil { if d.highSwaggerModel != nil && d.highOpenAPI3Model == nil {
return nil, nil, nil, []error{errors.New("this method only supports OpenAPI 3 documents, not Swagger")} return nil, errors.New("this method only supports OpenAPI 3 documents, not Swagger")
} }
var newBytes []byte var newBytes []byte
// render the model as the correct type based on the source.
// https://github.com/pb33f/libopenapi/issues/105
if d.info.SpecFileType == datamodel.JSONFileType { if d.info.SpecFileType == datamodel.JSONFileType {
jsonIndent := " " jsonIndent := " "
i := d.info.OriginalIndentation i := d.info.OriginalIndentation
@@ -190,17 +223,7 @@ func (d *document) RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Do
newBytes = d.highOpenAPI3Model.Model.RenderWithIndention(d.info.OriginalIndentation) newBytes = d.highOpenAPI3Model.Model.RenderWithIndention(d.info.OriginalIndentation)
} }
newDoc, err := NewDocumentWithConfiguration(newBytes, d.config) return newBytes, nil
if err != nil {
return newBytes, newDoc, nil, []error{err}
}
// build the model.
model, errs := newDoc.BuildV3Model()
if errs != nil {
return newBytes, newDoc, model, errs
}
// this document is now dead, long live the new document!
return newBytes, newDoc, model, nil
} }
func (d *document) BuildV2Model() (*DocumentModel[v2high.Swagger], []error) { func (d *document) BuildV2Model() (*DocumentModel[v2high.Swagger], []error) {

View File

@@ -8,7 +8,6 @@ import (
"github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/pb33f/libopenapi/what-changed/model" "github.com/pb33f/libopenapi/what-changed/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"io/ioutil"
"os" "os"
"strings" "strings"
"testing" "testing"
@@ -230,7 +229,7 @@ func TestDocument_RenderAndReload_ChangeCheck_Asana(t *testing.T) {
func TestDocument_RenderAndReload(t *testing.T) { func TestDocument_RenderAndReload(t *testing.T) {
// load an OpenAPI 3 specification from bytes // load an OpenAPI 3 specification from bytes
petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json") petstore, _ := os.ReadFile("test_specs/petstorev3.json")
// create a new document from specification bytes // create a new document from specification bytes
doc, err := NewDocument(petstore) doc, err := NewDocument(petstore)
@@ -276,10 +275,101 @@ func TestDocument_RenderAndReload(t *testing.T) {
assert.Equal(t, "https://pb33f.io", assert.Equal(t, "https://pb33f.io",
h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl) h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl)
}
func TestDocument_Render(t *testing.T) {
// load an OpenAPI 3 specification from bytes
petstore, _ := os.ReadFile("test_specs/petstorev3.json")
// create a new document from specification bytes
doc, err := NewDocument(petstore)
// if anything went wrong, an error is thrown
if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err))
}
// because we know this is a v3 spec, we can build a ready to go model from it.
m, _ := doc.BuildV3Model()
// mutate the model
h := m.Model
h.Paths.PathItems["/pet/findByStatus"].Get.OperationId = "findACakeInABakery"
h.Paths.PathItems["/pet/findByStatus"].Get.Responses.Codes["400"].Description = "a nice bucket of mice"
h.Paths.PathItems["/pet/findByTags"].Get.Tags =
append(h.Paths.PathItems["/pet/findByTags"].Get.Tags, "gurgle", "giggle")
h.Paths.PathItems["/pet/{petId}"].Delete.Security = append(h.Paths.PathItems["/pet/{petId}"].Delete.Security,
&base.SecurityRequirement{Requirements: map[string][]string{
"pizza-and-cake": {"read:abook", "write:asong"},
}})
h.Components.Schemas["Order"].Schema().Properties["status"].Schema().Example = "I am a teapot, filled with love."
h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl = "https://pb33f.io"
bytes, e := doc.Render()
assert.NoError(t, e)
assert.NotNil(t, bytes)
newDoc, docErr := NewDocument(bytes)
assert.NoError(t, docErr)
newDocModel, docErrs := newDoc.BuildV3Model()
assert.Len(t, docErrs, 0)
h = newDocModel.Model
assert.Equal(t, "findACakeInABakery", h.Paths.PathItems["/pet/findByStatus"].Get.OperationId)
assert.Equal(t, "a nice bucket of mice",
h.Paths.PathItems["/pet/findByStatus"].Get.Responses.Codes["400"].Description)
assert.Len(t, h.Paths.PathItems["/pet/findByTags"].Get.Tags, 3)
assert.Len(t, h.Paths.PathItems["/pet/findByTags"].Get.Tags, 3)
yu := h.Paths.PathItems["/pet/{petId}"].Delete.Security
assert.Equal(t, "read:abook", yu[len(yu)-1].Requirements["pizza-and-cake"][0])
assert.Equal(t, "I am a teapot, filled with love.",
h.Components.Schemas["Order"].Schema().Properties["status"].Schema().Example)
assert.Equal(t, "https://pb33f.io",
h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl)
}
func TestDocument_RenderWithLargeIndention(t *testing.T) {
json := `{
"openapi": "3.0"
}`
doc, _ := NewDocument([]byte(json))
doc.BuildV3Model()
bytes, _ := doc.Render()
assert.Equal(t, json, string(bytes))
} }
func TestDocument_Render_ChangeCheck_Burgershop(t *testing.T) {
bs, _ := os.ReadFile("test_specs/burgershop.openapi.yaml")
doc, _ := NewDocument(bs)
doc.BuildV3Model()
rend, _ := doc.Render()
newDoc, _ := NewDocument(rend)
// compare documents
compReport, errs := CompareDocuments(doc, newDoc)
// should noth be nil.
assert.Nil(t, errs)
assert.NotNil(t, rend)
assert.Nil(t, compReport)
}
func TestDocument_RenderAndReload_Swagger(t *testing.T) { func TestDocument_RenderAndReload_Swagger(t *testing.T) {
petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json") petstore, _ := os.ReadFile("test_specs/petstorev2.json")
doc, _ := NewDocument(petstore) doc, _ := NewDocument(petstore)
doc.BuildV2Model() doc.BuildV2Model()
doc.BuildV2Model() doc.BuildV2Model()
@@ -289,8 +379,19 @@ func TestDocument_RenderAndReload_Swagger(t *testing.T) {
} }
func TestDocument_Render_Swagger(t *testing.T) {
petstore, _ := os.ReadFile("test_specs/petstorev2.json")
doc, _ := NewDocument(petstore)
doc.BuildV2Model()
doc.BuildV2Model()
_, e := doc.Render()
assert.Error(t, e)
assert.Equal(t, "this method only supports OpenAPI 3 documents, not Swagger", e.Error())
}
func TestDocument_BuildModelPreBuild(t *testing.T) { func TestDocument_BuildModelPreBuild(t *testing.T) {
petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json") petstore, _ := os.ReadFile("test_specs/petstorev3.json")
doc, e := NewDocument(petstore) doc, e := NewDocument(petstore)
assert.NoError(t, e) assert.NoError(t, e)
doc.BuildV3Model() doc.BuildV3Model()
@@ -314,7 +415,7 @@ func TestDocument_AnyDocWithConfig(t *testing.T) {
} }
func TestDocument_BuildModelCircular(t *testing.T) { func TestDocument_BuildModelCircular(t *testing.T) {
petstore, _ := ioutil.ReadFile("test_specs/circular-tests.yaml") petstore, _ := os.ReadFile("test_specs/circular-tests.yaml")
doc, _ := NewDocument(petstore) doc, _ := NewDocument(petstore)
m, e := doc.BuildV3Model() m, e := doc.BuildV3Model()
assert.NotNil(t, m) assert.NotNil(t, m)
@@ -322,7 +423,7 @@ func TestDocument_BuildModelCircular(t *testing.T) {
} }
func TestDocument_BuildModelBad(t *testing.T) { func TestDocument_BuildModelBad(t *testing.T) {
petstore, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml") petstore, _ := os.ReadFile("test_specs/badref-burgershop.openapi.yaml")
doc, _ := NewDocument(petstore) doc, _ := NewDocument(petstore)
m, e := doc.BuildV3Model() m, e := doc.BuildV3Model()
assert.Nil(t, m) assert.Nil(t, m)
@@ -387,8 +488,8 @@ paths:
} }
func TestDocument_BuildModel_CompareDocsV3_LeftError(t *testing.T) { func TestDocument_BuildModel_CompareDocsV3_LeftError(t *testing.T) {
burgerShopOriginal, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml") burgerShopOriginal, _ := os.ReadFile("test_specs/badref-burgershop.openapi.yaml")
burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml") burgerShopUpdated, _ := os.ReadFile("test_specs/burgershop.openapi-modified.yaml")
originalDoc, _ := NewDocument(burgerShopOriginal) originalDoc, _ := NewDocument(burgerShopOriginal)
updatedDoc, _ := NewDocument(burgerShopUpdated) updatedDoc, _ := NewDocument(burgerShopUpdated)
changes, errors := CompareDocuments(originalDoc, updatedDoc) changes, errors := CompareDocuments(originalDoc, updatedDoc)
@@ -398,8 +499,8 @@ func TestDocument_BuildModel_CompareDocsV3_LeftError(t *testing.T) {
func TestDocument_BuildModel_CompareDocsV3_RightError(t *testing.T) { func TestDocument_BuildModel_CompareDocsV3_RightError(t *testing.T) {
burgerShopOriginal, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml") burgerShopOriginal, _ := os.ReadFile("test_specs/badref-burgershop.openapi.yaml")
burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml") burgerShopUpdated, _ := os.ReadFile("test_specs/burgershop.openapi-modified.yaml")
originalDoc, _ := NewDocument(burgerShopOriginal) originalDoc, _ := NewDocument(burgerShopOriginal)
updatedDoc, _ := NewDocument(burgerShopUpdated) updatedDoc, _ := NewDocument(burgerShopUpdated)
changes, errors := CompareDocuments(updatedDoc, originalDoc) changes, errors := CompareDocuments(updatedDoc, originalDoc)
@@ -410,8 +511,8 @@ func TestDocument_BuildModel_CompareDocsV3_RightError(t *testing.T) {
func TestDocument_BuildModel_CompareDocsV2_Error(t *testing.T) { func TestDocument_BuildModel_CompareDocsV2_Error(t *testing.T) {
burgerShopOriginal, _ := ioutil.ReadFile("test_specs/petstorev2-badref.json") burgerShopOriginal, _ := os.ReadFile("test_specs/petstorev2-badref.json")
burgerShopUpdated, _ := ioutil.ReadFile("test_specs/petstorev2-badref.json") burgerShopUpdated, _ := os.ReadFile("test_specs/petstorev2-badref.json")
originalDoc, _ := NewDocument(burgerShopOriginal) originalDoc, _ := NewDocument(burgerShopOriginal)
updatedDoc, _ := NewDocument(burgerShopUpdated) updatedDoc, _ := NewDocument(burgerShopUpdated)
changes, errors := CompareDocuments(updatedDoc, originalDoc) changes, errors := CompareDocuments(updatedDoc, originalDoc)
@@ -422,8 +523,8 @@ func TestDocument_BuildModel_CompareDocsV2_Error(t *testing.T) {
func TestDocument_BuildModel_CompareDocsV2V3Mix_Error(t *testing.T) { func TestDocument_BuildModel_CompareDocsV2V3Mix_Error(t *testing.T) {
burgerShopOriginal, _ := ioutil.ReadFile("test_specs/petstorev2.json") burgerShopOriginal, _ := os.ReadFile("test_specs/petstorev2.json")
burgerShopUpdated, _ := ioutil.ReadFile("test_specs/petstorev3.json") burgerShopUpdated, _ := os.ReadFile("test_specs/petstorev3.json")
originalDoc, _ := NewDocument(burgerShopOriginal) originalDoc, _ := NewDocument(burgerShopOriginal)
updatedDoc, _ := NewDocument(burgerShopUpdated) updatedDoc, _ := NewDocument(burgerShopUpdated)
changes, errors := CompareDocuments(updatedDoc, originalDoc) changes, errors := CompareDocuments(updatedDoc, originalDoc)
@@ -433,7 +534,7 @@ func TestDocument_BuildModel_CompareDocsV2V3Mix_Error(t *testing.T) {
} }
func TestSchemaRefIsFollowed(t *testing.T) { func TestSchemaRefIsFollowed(t *testing.T) {
petstore, _ := ioutil.ReadFile("test_specs/ref-followed.yaml") petstore, _ := os.ReadFile("test_specs/ref-followed.yaml")
// create a new document from specification bytes // create a new document from specification bytes
document, err := NewDocument(petstore) document, err := NewDocument(petstore)