Added mutate command to low level API

This simple method gives the low API a super powerful and simple way to mutate the value of any node, which is then reflected in the root node, can than be serialized again and, voila! now our spec is editable.
This commit is contained in:
Dave Shanley
2022-09-13 09:15:55 -04:00
parent 647541cc77
commit a2b7119af7
5 changed files with 128 additions and 51 deletions

View File

@@ -40,6 +40,10 @@ func (n NodeReference[T]) GenerateMapKey() string {
return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column) return fmt.Sprintf("%d:%d", n.ValueNode.Line, n.ValueNode.Column)
} }
func (n NodeReference[T]) Mutate(value T) {
n.ValueNode.Value = fmt.Sprintf("%v", value)
}
func (n ValueReference[T]) IsEmpty() bool { func (n ValueReference[T]) IsEmpty() bool {
return n.ValueNode == nil return n.ValueNode == nil
} }
@@ -56,6 +60,10 @@ func (n KeyReference[T]) GenerateMapKey() string {
return fmt.Sprintf("%d:%d", n.KeyNode.Line, n.KeyNode.Column) return fmt.Sprintf("%d:%d", n.KeyNode.Line, n.KeyNode.Column)
} }
func (n ValueReference[T]) Mutate(value T) {
n.ValueNode.Value = fmt.Sprintf("%v", value)
}
func IsCircular(node *yaml.Node, idx *index.SpecIndex) bool { func IsCircular(node *yaml.Node, idx *index.SpecIndex) bool {
if idx == nil { if idx == nil {
return false // no index! nothing we can do. return false // no index! nothing we can do.

View File

@@ -13,6 +13,11 @@ import (
"time" "time"
) )
const (
JSONFileType = "json"
YAMLFileType = "yaml"
)
// SpecInfo represents a 'ready-to-process' OpenAPI Document. // SpecInfo represents a 'ready-to-process' OpenAPI Document.
type SpecInfo struct { type SpecInfo struct {
SpecType string `json:"type"` SpecType string `json:"type"`
@@ -59,9 +64,9 @@ func ExtractSpecInfo(spec []byte) (*SpecInfo, error) {
} }
if runes[0] == '{' && runes[len(runes)-1] == '}' { if runes[0] == '{' && runes[len(runes)-1] == '}' {
specVersion.SpecFileType = "json" specVersion.SpecFileType = JSONFileType
} else { } else {
specVersion.SpecFileType = "yaml" specVersion.SpecFileType = YAMLFileType
} }
err := yaml.Unmarshal(spec, &parsedSpec) err := yaml.Unmarshal(spec, &parsedSpec)

View File

@@ -10,6 +10,8 @@ import (
v3high "github.com/pb33f/libopenapi/datamodel/high/3.0" v3high "github.com/pb33f/libopenapi/datamodel/high/3.0"
v2low "github.com/pb33f/libopenapi/datamodel/low/2.0" v2low "github.com/pb33f/libopenapi/datamodel/low/2.0"
v3low "github.com/pb33f/libopenapi/datamodel/low/3.0" v3low "github.com/pb33f/libopenapi/datamodel/low/3.0"
"github.com/pb33f/libopenapi/utils"
"gopkg.in/yaml.v3"
) )
type Document struct { type Document struct {
@@ -40,6 +42,18 @@ func (d *Document) GetSpecInfo() *datamodel.SpecInfo {
return d.info return d.info
} }
func (d *Document) Serialize() ([]byte, error) {
if d.info == nil {
return nil, fmt.Errorf("unable to serialize, document has not yet been initialized")
}
if d.info.SpecFileType == datamodel.YAMLFileType {
return yaml.Marshal(d.info.RootNode)
} else {
yamlData, _ := yaml.Marshal(d.info.RootNode)
return utils.ConvertYAMLtoJSON(yamlData)
}
}
func (d *Document) BuildV2Document() (*DocumentModel[v2high.Swagger], []error) { func (d *Document) BuildV2Document() (*DocumentModel[v2high.Swagger], []error) {
var errors []error var errors []error
if d.info == nil { if d.info == nil {

View File

@@ -106,3 +106,63 @@ paths:
assert.Len(t, docErr, 1) assert.Len(t, docErr, 1)
assert.Nil(t, v3Doc) assert.Nil(t, v3Doc)
} }
func TestDocument_Serialize_Error(t *testing.T) {
doc := new(Document) // not how this should be instantiated.
_, err := doc.Serialize()
assert.Error(t, err)
}
func TestDocument_Serialize(t *testing.T) {
yml := `openapi: 3.0
info:
title: The magic API
`
doc, _ := NewDocument([]byte(yml))
serial, err := doc.Serialize()
assert.NoError(t, err)
assert.Equal(t, yml, string(serial))
}
func TestDocument_Serialize_Modified(t *testing.T) {
yml := `openapi: 3.0
info:
title: The magic API
`
ymlModified := `openapi: 3.0
info:
title: The magic API - but now, altered!
`
doc, _ := NewDocument([]byte(yml))
v3Doc, _ := doc.BuildV3Document()
v3Doc.Model.Info.GoLow().Title.Mutate("The magic API - but now, altered!")
serial, err := doc.Serialize()
assert.NoError(t, err)
assert.Equal(t, ymlModified, string(serial))
}
func TestDocument_Serialize_JSON_Modified(t *testing.T) {
json := `{ 'openapi': '3.0',
'info': {
'title': 'The magic API'
}
}
`
jsonModified := `{"info":{"title":"The magic API - but now, altered!"},"openapi":"3.0"}`
doc, _ := NewDocument([]byte(json))
v3Doc, _ := doc.BuildV3Document()
v3Doc.Model.Info.GoLow().Title.Mutate("The magic API - but now, altered!")
serial, err := doc.Serialize()
assert.NoError(t, err)
assert.Equal(t, jsonModified, string(serial))
}

View File

@@ -1,51 +1,41 @@
package main package main
import ( //func main() {
"fmt" //
"github.com/pb33f/libopenapi/datamodel" // testData := `openapi: 3.0.1
high "github.com/pb33f/libopenapi/datamodel/high/3.0" //info:
low "github.com/pb33f/libopenapi/datamodel/low/3.0" // title: this is a title
"gopkg.in/yaml.v3" // description: this is a description
"io/ioutil" //tags:
"os" // - name: Tag A
) // description: cake
// x-hack: true
func main() { // - name: Tag B
// description: coffee
testData := `openapi: 3.0.1 // x-code: hack`
info: //
title: this is a title // data := []byte(testData)
description: this is a description // _ = ioutil.WriteFile("sample.yaml", data, 0664)
tags: //
- name: Tag A // info, _ := datamodel.ExtractSpecInfo(data)
description: cake // lowDoc, err := low.CreateDocument(info)
x-hack: true // if len(err) > 0 {
- name: Tag B // for e := range err {
description: coffee // fmt.Printf("%e\n", err[e])
x-code: hack` // }
// return
data := []byte(testData) // }
_ = ioutil.WriteFile("sample.yaml", data, 0664) // highDoc := high.NewDocument(lowDoc)
//
info, _ := datamodel.ExtractSpecInfo(data) // highDoc.Info.GoLow().Title.ValueNode.Value = "let's hack this"
lowDoc, err := low.CreateDocument(info) // //highDoc.Tags[0].SetName("We are a new name now")
if len(err) > 0 { // //highDoc.Tags[0].SetDescription("and a new description")
for e := range err { //
fmt.Printf("%e\n", err[e]) // //newTag := lowDoc.AddTag()
} // //fmt.Println(newTag)
return // modified, _ := yaml.Marshal(info.RootNode)
} // fmt.Println(string(modified))
highDoc := high.NewDocument(lowDoc) //
// os.Remove("sample.yaml")
highDoc.Info.GoLow().Title.ValueNode.Value = "let's hack this" //
//highDoc.Tags[0].SetName("We are a new name now") //}
//highDoc.Tags[0].SetDescription("and a new description")
//newTag := lowDoc.AddTag()
//fmt.Println(newTag)
modified, _ := yaml.Marshal(info.RootNode)
fmt.Println(string(modified))
os.Remove("sample.yaml")
}