Added support for original format rendering #105

Original input document types are now rendered automatically as JSON or YAML (vs always being YAML). This only operates at the `Document` level for rendering out entire documents.

Requested in #105

Signed-off-by: Dave Shanley <dave@quobix.com>
This commit is contained in:
Dave Shanley
2023-06-16 06:54:54 -04:00
committed by quobix
parent fcf2f332d9
commit 5b128c098a
7 changed files with 85 additions and 33 deletions

View File

@@ -14,6 +14,7 @@ import (
"github.com/pb33f/libopenapi/datamodel/high/base"
low "github.com/pb33f/libopenapi/datamodel/low/v3"
"github.com/pb33f/libopenapi/index"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
)
@@ -153,6 +154,15 @@ func (d *Document) Render() ([]byte, error) {
return yaml.Marshal(d)
}
// RenderJSON will return a JSON representation of the Document object as a byte slice.
func (d *Document) RenderJSON() ([]byte, error) {
yamlData, err := yaml.Marshal(d)
if err != nil {
return yamlData, err
}
return utils.ConvertYAMLtoJSONPretty(yamlData, "", " ")
}
func (d *Document) RenderInline() ([]byte, error) {
di, _ := d.MarshalYAMLInline()
return yaml.Marshal(di)

View File

@@ -108,18 +108,3 @@ func (d *Document) GetExternalDocs() *low.NodeReference[any] {
Value: d.ExternalDocs.Value,
}
}
// TODO: this is early prototype mutation/modification code, keeping it around for later.
//func (d *Document) AddTag() *base.Tag {
// t := base.NewTag()
// //d.Tags.KeyNode
// t.Name.Value = "nice new tag"
//
// dat, _ := yaml.Marshal(t)
// var inject yaml.Node
// _ = yaml.Unmarshal(dat, &inject)
//
// d.Tags.ValueNode.Content = append(d.Tags.ValueNode.Content, inject.Content[0])
//
// return t
//}

View File

@@ -160,10 +160,23 @@ func (d *document) RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Do
if d.highSwaggerModel != nil && d.highOpenAPI3Model == nil {
return nil, nil, nil, []error{errors.New("this method only supports OpenAPI 3 documents, not Swagger")}
}
newBytes, err := d.highOpenAPI3Model.Model.Render()
if err != nil {
return newBytes, nil, nil, []error{err}
var newBytes []byte
var renderError error
// render the model as the correct type based on the source.
// https://github.com/pb33f/libopenapi/issues/105
if d.info.SpecFileType == datamodel.JSONFileType {
newBytes, renderError = d.highOpenAPI3Model.Model.RenderJSON()
}
if d.info.SpecFileType == datamodel.YAMLFileType {
newBytes, renderError = d.highOpenAPI3Model.Model.Render()
}
if renderError != nil {
return newBytes, nil, nil, []error{renderError}
}
newDoc, err := NewDocumentWithConfiguration(newBytes, d.config)
if err != nil {
return newBytes, newDoc, nil, []error{err}

View File

@@ -6,7 +6,6 @@ package libopenapi
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel"
"io/ioutil"
"net/url"
"os"
"strings"
@@ -26,7 +25,7 @@ func ExampleNewDocument_fromOpenAPI3Document() {
// How to read in an OpenAPI 3 Specification, into a Document.
// 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
document, err := NewDocument(petstore)
@@ -62,7 +61,7 @@ func ExampleNewDocument_fromWithDocumentConfigurationFailure() {
// from files or the network
// load in the Digital Ocean OpenAPI specification
digitalOcean, _ := ioutil.ReadFile("test_specs/digitalocean.yaml")
digitalOcean, _ := os.ReadFile("test_specs/digitalocean.yaml")
// create a DocumentConfiguration that prevents loading file and remote references
config := datamodel.DocumentConfiguration{
@@ -94,7 +93,7 @@ func ExampleNewDocument_fromWithDocumentConfigurationSuccess() {
// from files or the network
// load in the Digital Ocean OpenAPI specification
digitalOcean, _ := ioutil.ReadFile("test_specs/digitalocean.yaml")
digitalOcean, _ := os.ReadFile("test_specs/digitalocean.yaml")
// Digital Ocean needs a baseURL to be set, so we can resolve relative references.
baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification")
@@ -132,7 +131,7 @@ func ExampleNewDocument_fromSwaggerDocument() {
// How to read in a Swagger / OpenAPI 2 Specification, into a Document.
// load a Swagger specification from bytes
petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json")
petstore, _ := os.ReadFile("test_specs/petstorev2.json")
// create a new document from specification bytes
document, err := NewDocument(petstore)
@@ -165,7 +164,7 @@ func ExampleNewDocument_fromSwaggerDocument() {
func ExampleNewDocument_fromUnknownVersion() {
// load an unknown version of an OpenAPI spec
petstore, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml")
petstore, _ := os.ReadFile("test_specs/burgershop.openapi.yaml")
// create a new document from specification bytes
document, err := NewDocument(petstore)
@@ -226,7 +225,7 @@ info:
name: Some Person
email: some@emailaddress.com
license:
url: http://some-place-on-the-internet.com/license
url: https://some-place-on-the-internet.com/license
`
// create a new document from specification bytes
document, err := NewDocument([]byte(spec))
@@ -284,10 +283,10 @@ func ExampleCompareDocuments_openAPI() {
// How to compare two different OpenAPI specifications.
// load an original OpenAPI 3 specification from bytes
burgerShopOriginal, _ := ioutil.ReadFile("test_specs/burgershop.openapi.yaml")
burgerShopOriginal, _ := os.ReadFile("test_specs/burgershop.openapi.yaml")
// load an **updated** OpenAPI 3 specification from bytes
burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml")
burgerShopUpdated, _ := os.ReadFile("test_specs/burgershop.openapi-modified.yaml")
// create a new document from original specification bytes
originalDoc, err := NewDocument(burgerShopOriginal)
@@ -331,10 +330,10 @@ func ExampleCompareDocuments_swagger() {
// How to compare two different Swagger specifications.
// load an original OpenAPI 3 specification from bytes
petstoreOriginal, _ := ioutil.ReadFile("test_specs/petstorev2-complete.yaml")
petstoreOriginal, _ := os.ReadFile("test_specs/petstorev2-complete.yaml")
// load an **updated** OpenAPI 3 specification from bytes
petstoreUpdated, _ := ioutil.ReadFile("test_specs/petstorev2-complete-modified.yaml")
petstoreUpdated, _ := os.ReadFile("test_specs/petstorev2-complete-modified.yaml")
// create a new document from original specification bytes
originalDoc, err := NewDocument(petstoreOriginal)
@@ -647,9 +646,6 @@ func ExampleNewDocument_modifyAndReRender() {
// add the path to the document
v3Model.Model.Paths.PathItems["/new/path"] = newPath
// render out the new path item to YAML
// renderedPathItem, _ := yaml.Marshal(newPath)
// render the document back to bytes and reload the model.
rawBytes, _, newModel, errs := doc.RenderAndReload()
@@ -665,5 +661,5 @@ func ExampleNewDocument_modifyAndReRender() {
fmt.Printf("There were %d original paths. There are now %d paths in the document\n", originalPaths, newPaths)
fmt.Printf("The original spec had %d bytes, the new one has %d\n", len(petstore), len(rawBytes))
// Output: There were 13 original paths. There are now 14 paths in the document
//The original spec had 31143 bytes, the new one has 27841
//The original spec had 31143 bytes, the new one has 31027
}

View File

@@ -573,6 +573,7 @@ summary: a test thing
description: this is a test, that does a test.`
_ = os.WriteFile("test-operation.yaml", []byte(ae), 0644)
defer os.Remove("test-operation.yaml")
var d = `openapi: "3.1"
paths:
@@ -595,3 +596,29 @@ paths:
assert.Equal(t, d, strings.TrimSpace(string(rend)))
}
func TestDocument_InputAsJSON(t *testing.T) {
var d = `{
"openapi": "3.1",
"paths": {
"/an/operation": {
"get": {
"operationId": "thisIsAnOperationId"
}
}
}
}`
doc, err := NewDocumentWithConfiguration([]byte(d), datamodel.NewOpenDocumentConfiguration())
if err != nil {
panic(err)
}
_, _ = doc.BuildV3Model()
// render the document.
rend, _, _, _ := doc.RenderAndReload()
assert.Equal(t, d, strings.TrimSpace(string(rend)))
}

View File

@@ -503,6 +503,19 @@ func ConvertYAMLtoJSON(yamlData []byte) ([]byte, error) {
return jsonData, nil
}
// ConvertYAMLtoJSONPretty will do exactly what you think it will. It will deserialize YAML into serialized JSON.
// However, this version will a apply prefix/indentation to the JSON.
func ConvertYAMLtoJSONPretty(yamlData []byte, prefix string, indent string) ([]byte, error) {
var decodedYaml map[string]interface{}
err := yaml.Unmarshal(yamlData, &decodedYaml)
if err != nil {
return nil, err
}
// if the data can be decoded, it can be encoded (that's my view anyway). no need for an error check.
jsonData, _ := json.MarshalIndent(decodedYaml, prefix, indent)
return jsonData, nil
}
// IsHttpVerb will check if an operation is valid or not.
func IsHttpVerb(verb string) bool {
verbs := []string{"get", "post", "put", "patch", "delete", "options", "trace", "head"}

View File

@@ -637,6 +637,14 @@ func TestConvertYAMLtoJSON(t *testing.T) {
assert.Nil(t, str)
}
func TestConvertYAMLtoJSONPretty(t *testing.T) {
str, err := ConvertYAMLtoJSONPretty([]byte("hello: there"), "", " ")
assert.NoError(t, err)
assert.NotNil(t, str)
assert.Equal(t, "{\n \"hello\": \"there\"\n}", string(str))
}
func TestIsHttpVerb(t *testing.T) {
assert.True(t, IsHttpVerb("get"))
assert.True(t, IsHttpVerb("post"))