mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-06 04:20:11 +00:00
fix: continued moving everything to orderedmaps plus cleaned up most the tests
This commit is contained in:
130
orderedmap/builder.go
Normal file
130
orderedmap/builder.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package orderedmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pb33f/libopenapi/datamodel/high/nodes"
|
||||
"github.com/pb33f/libopenapi/utils"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Marshaler interface {
|
||||
MarshalYAML() (interface{}, error)
|
||||
}
|
||||
|
||||
type NodeBuilder interface {
|
||||
AddYAMLNode(parent *yaml.Node, entry *nodes.NodeEntry) *yaml.Node
|
||||
}
|
||||
|
||||
type MapToYamlNoder interface {
|
||||
ToYamlNode(n NodeBuilder, l any) *yaml.Node
|
||||
}
|
||||
|
||||
type HasKeyNode interface {
|
||||
GetKeyNode() *yaml.Node
|
||||
}
|
||||
|
||||
type HasValueNode interface {
|
||||
GetValueNode() *yaml.Node
|
||||
}
|
||||
|
||||
type HasValueUntyped interface {
|
||||
GetValueUntyped() any
|
||||
}
|
||||
|
||||
type FindValueUntyped interface {
|
||||
FindValueUntyped(k string) any
|
||||
}
|
||||
|
||||
func (o *Map[K, V]) ToYamlNode(n NodeBuilder, l any) *yaml.Node {
|
||||
p := utils.CreateEmptyMapNode()
|
||||
|
||||
var vn *yaml.Node
|
||||
|
||||
i := 99999
|
||||
if l != nil {
|
||||
if hvn, ok := l.(HasValueNode); ok {
|
||||
vn = hvn.GetValueNode()
|
||||
if vn != nil && len(vn.Content) > 0 {
|
||||
i = vn.Content[0].Line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for pair := o.First(); pair != nil; pair = pair.Next() {
|
||||
var k any = pair.Key()
|
||||
if m, ok := k.(Marshaler); ok { // TODO marshal inline?
|
||||
k, _ = m.MarshalYAML()
|
||||
}
|
||||
|
||||
var y any
|
||||
y, ok := k.(yaml.Node)
|
||||
if !ok {
|
||||
y, ok = k.(*yaml.Node)
|
||||
}
|
||||
if ok {
|
||||
b, _ := yaml.Marshal(y)
|
||||
k = strings.TrimSpace(string(b))
|
||||
}
|
||||
|
||||
ks := k.(string)
|
||||
|
||||
var keyStyle yaml.Style
|
||||
keyNode := findKeyNode(ks, vn)
|
||||
if keyNode != nil {
|
||||
keyStyle = keyNode.Style
|
||||
}
|
||||
|
||||
var lv any
|
||||
if l != nil {
|
||||
if hvut, ok := l.(HasValueUntyped); ok {
|
||||
vut := hvut.GetValueUntyped()
|
||||
if m, ok := vut.(FindValueUntyped); ok {
|
||||
lv = m.FindValueUntyped(ks)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
n.AddYAMLNode(p, &nodes.NodeEntry{
|
||||
Tag: ks,
|
||||
Key: ks,
|
||||
Line: i,
|
||||
Value: pair.Value(),
|
||||
KeyStyle: keyStyle,
|
||||
LowValue: lv,
|
||||
})
|
||||
i++
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func findKeyNode(key string, m *yaml.Node) *yaml.Node {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.Content); i += 2 {
|
||||
if m.Content[i].Value == key {
|
||||
return m.Content[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Map[K, V]) FindValueUntyped(key string) any {
|
||||
for pair := o.First(); pair != nil; pair = pair.Next() {
|
||||
var k any = pair.Key()
|
||||
if hvut, ok := k.(HasValueUntyped); ok {
|
||||
if fmt.Sprintf("%v", hvut.GetValueUntyped()) == key {
|
||||
return pair.Value()
|
||||
}
|
||||
}
|
||||
if fmt.Sprintf("%v", k) == key {
|
||||
return pair.Value()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -6,26 +6,14 @@ package orderedmap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
wk8orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
)
|
||||
|
||||
type Map[K comparable, V any] interface {
|
||||
Lengthiness
|
||||
Get(K) (V, bool)
|
||||
GetOrZero(K) V
|
||||
Set(K, V) (V, bool)
|
||||
Delete(K) (V, bool)
|
||||
First() Pair[K, V]
|
||||
}
|
||||
|
||||
type Lengthiness interface {
|
||||
Len() int
|
||||
}
|
||||
|
||||
type Pair[K comparable, V any] interface {
|
||||
Key() K
|
||||
KeyPtr() *K
|
||||
@@ -34,7 +22,7 @@ type Pair[K comparable, V any] interface {
|
||||
Next() Pair[K, V]
|
||||
}
|
||||
|
||||
type wrapOrderedMap[K comparable, V any] struct {
|
||||
type Map[K comparable, V any] struct {
|
||||
*wk8orderedmap.OrderedMap[K, V]
|
||||
}
|
||||
|
||||
@@ -42,20 +30,22 @@ type wrapPair[K comparable, V any] struct {
|
||||
*wk8orderedmap.Pair[K, V]
|
||||
}
|
||||
|
||||
type (
|
||||
ActionFunc[K comparable, V any] func(Pair[K, V]) error
|
||||
TranslateFunc[IN any, OUT any] func(IN) (OUT, error)
|
||||
ResultFunc[V any] func(V) error
|
||||
)
|
||||
|
||||
// New creates an ordered map generic object.
|
||||
func New[K comparable, V any]() Map[K, V] {
|
||||
return &wrapOrderedMap[K, V]{
|
||||
func New[K comparable, V any]() *Map[K, V] {
|
||||
return &Map[K, V]{
|
||||
OrderedMap: wk8orderedmap.New[K, V](),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *wrapOrderedMap[K, V]) GetOrZero(k K) V {
|
||||
func (o *Map[K, V]) GetKeyType() reflect.Type {
|
||||
return reflect.TypeOf(new(K))
|
||||
}
|
||||
|
||||
func (o *Map[K, V]) GetValueType() reflect.Type {
|
||||
return reflect.TypeOf(new(V))
|
||||
}
|
||||
|
||||
func (o *Map[K, V]) GetOrZero(k K) V {
|
||||
v, ok := o.OrderedMap.Get(k)
|
||||
if !ok {
|
||||
var zero V
|
||||
@@ -64,7 +54,7 @@ func (o *wrapOrderedMap[K, V]) GetOrZero(k K) V {
|
||||
return v
|
||||
}
|
||||
|
||||
func (o *wrapOrderedMap[K, V]) First() Pair[K, V] {
|
||||
func (o *Map[K, V]) First() Pair[K, V] {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -90,7 +80,7 @@ func NewPair[K comparable, V any](key K, value V) Pair[K, V] {
|
||||
|
||||
// FromPairs creates an `OrderedMap` from an array of pairs.
|
||||
// Use `NewPair()` to generate input parameters.
|
||||
func FromPairs[K comparable, V any](pairs ...Pair[K, V]) Map[K, V] {
|
||||
func FromPairs[K comparable, V any](pairs ...Pair[K, V]) *Map[K, V] {
|
||||
om := New[K, V]()
|
||||
for _, pair := range pairs {
|
||||
om.Set(pair.Key(), pair.Value())
|
||||
@@ -99,8 +89,8 @@ func FromPairs[K comparable, V any](pairs ...Pair[K, V]) Map[K, V] {
|
||||
}
|
||||
|
||||
// IsZero is required to support `omitempty` tag for YAML/JSON marshaling.
|
||||
func (o *wrapOrderedMap[K, V]) IsZero() bool {
|
||||
return o.Len() == 0
|
||||
func (o *Map[K, V]) IsZero() bool {
|
||||
return Len(o) == 0
|
||||
}
|
||||
|
||||
func (p *wrapPair[K, V]) Next() Pair[K, V] {
|
||||
@@ -131,19 +121,18 @@ func (p *wrapPair[K, V]) ValuePtr() *V {
|
||||
|
||||
// Len returns the length of a container implementing a `Len()` method.
|
||||
// Safely returns zero on nil pointer.
|
||||
func Len(l Lengthiness) int {
|
||||
if l == nil {
|
||||
func Len[K comparable, V any](m *Map[K, V]) int {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
return l.Len()
|
||||
return m.Len()
|
||||
}
|
||||
|
||||
// ToOrderedMap converts map built-in to OrderedMap.
|
||||
// Iterate the map in order.
|
||||
// Safely handles nil pointer.
|
||||
// Be sure to iterate to end or cancel the context when done to release
|
||||
// resources.
|
||||
func Iterate[K comparable, V any](ctx context.Context, m Map[K, V]) <-chan Pair[K, V] {
|
||||
func Iterate[K comparable, V any](ctx context.Context, m *Map[K, V]) <-chan Pair[K, V] {
|
||||
c := make(chan Pair[K, V])
|
||||
if Len(m) == 0 {
|
||||
close(c)
|
||||
@@ -163,7 +152,7 @@ func Iterate[K comparable, V any](ctx context.Context, m Map[K, V]) <-chan Pair[
|
||||
}
|
||||
|
||||
// ToOrderedMap converts a `map` to `OrderedMap`.
|
||||
func ToOrderedMap[K comparable, V any](m map[K]V) Map[K, V] {
|
||||
func ToOrderedMap[K comparable, V any](m map[K]V) *Map[K, V] {
|
||||
om := New[K, V]()
|
||||
for k, v := range m {
|
||||
om.Set(k, v)
|
||||
@@ -173,7 +162,7 @@ func ToOrderedMap[K comparable, V any](m map[K]V) Map[K, V] {
|
||||
|
||||
// First returns map's first pair for iteration.
|
||||
// Safely handles nil pointer.
|
||||
func First[K comparable, V any](m Map[K, V]) Pair[K, V] {
|
||||
func First[K comparable, V any](m *Map[K, V]) Pair[K, V] {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -181,12 +170,12 @@ func First[K comparable, V any](m Map[K, V]) Pair[K, V] {
|
||||
}
|
||||
|
||||
// Cast converts `any` to `Map`.
|
||||
func Cast[K comparable, V any](v any) Map[K, V] {
|
||||
func Cast[K comparable, V any](v any) *Map[K, V] {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m, ok := v.(*wrapOrderedMap[K, V])
|
||||
m, ok := v.(*Map[K, V])
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@@ -194,90 +183,33 @@ func Cast[K comparable, V any](v any) Map[K, V] {
|
||||
return m
|
||||
}
|
||||
|
||||
type jobStatus[T any] struct {
|
||||
done chan struct{}
|
||||
result T
|
||||
}
|
||||
|
||||
// TranslateMapParallel iterates a `Map` in parallel and calls translate()
|
||||
// asynchronously.
|
||||
// translate() or result() may return `io.EOF` to break iteration.
|
||||
// Safely handles nil pointer.
|
||||
// Results are provided sequentially to result() in stable order from `Map`.
|
||||
func TranslateMapParallel[K comparable, V any, RV any](m Map[K, V], translate TranslateFunc[Pair[K, V], RV], result ResultFunc[RV]) error {
|
||||
func SortAlpha[K comparable, V any](m *Map[K, V]) *Map[K, V] {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
concurrency := runtime.NumCPU()
|
||||
c := Iterate(ctx, m)
|
||||
jobChan := make(chan *jobStatus[RV], concurrency)
|
||||
var reterr error
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
om := New[K, V]()
|
||||
|
||||
// Fan out translate jobs.
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
close(jobChan)
|
||||
wg.Done()
|
||||
}()
|
||||
for pair := range c {
|
||||
j := &jobStatus[RV]{
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
select {
|
||||
case jobChan <- j:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(pair Pair[K, V]) {
|
||||
value, err := translate(pair)
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
defer func() {
|
||||
mu.Unlock()
|
||||
wg.Done()
|
||||
cancel()
|
||||
}()
|
||||
if reterr == nil {
|
||||
reterr = err
|
||||
}
|
||||
return
|
||||
}
|
||||
j.result = value
|
||||
close(j.done)
|
||||
wg.Done()
|
||||
}(pair)
|
||||
}
|
||||
}()
|
||||
|
||||
// Iterate jobChan as jobs complete.
|
||||
defer wg.Wait()
|
||||
JOBLOOP:
|
||||
for j := range jobChan {
|
||||
select {
|
||||
case <-j.done:
|
||||
err := result(j.result)
|
||||
if err != nil {
|
||||
cancel()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
break JOBLOOP
|
||||
}
|
||||
type key struct {
|
||||
key string
|
||||
k K
|
||||
}
|
||||
|
||||
if reterr == io.EOF {
|
||||
return nil
|
||||
keys := []key{}
|
||||
for pair := m.First(); pair != nil; pair = pair.Next() {
|
||||
keys = append(keys, key{
|
||||
key: fmt.Sprintf("%v", pair.Key()),
|
||||
k: pair.Key(),
|
||||
})
|
||||
}
|
||||
return reterr
|
||||
|
||||
slices.SortFunc(keys, func(a, b key) int {
|
||||
return strings.Compare(a.key, b.key)
|
||||
})
|
||||
|
||||
for _, k := range keys {
|
||||
om.Set(k.k, m.GetOrZero(k.k))
|
||||
}
|
||||
|
||||
return om
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pb33f/libopenapi/datamodel"
|
||||
"github.com/pb33f/libopenapi/orderedmap"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -90,7 +91,7 @@ func TestMap(t *testing.T) {
|
||||
assert.Equal(t, mapSize, orderedmap.Len(m))
|
||||
|
||||
t.Run("Nil pointer", func(t *testing.T) {
|
||||
var m orderedmap.Map[string, int]
|
||||
var m *orderedmap.Map[string, int]
|
||||
assert.Zero(t, orderedmap.Len(m))
|
||||
})
|
||||
})
|
||||
@@ -180,7 +181,7 @@ func TestMap(t *testing.T) {
|
||||
resultCounter++
|
||||
return nil
|
||||
}
|
||||
err := orderedmap.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
|
||||
err := datamodel.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(mapSize), translateCounter)
|
||||
assert.Equal(t, mapSize, resultCounter)
|
||||
@@ -200,7 +201,7 @@ func TestMap(t *testing.T) {
|
||||
resultCounter++
|
||||
return nil
|
||||
}
|
||||
err := orderedmap.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
|
||||
err := datamodel.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
|
||||
require.ErrorContains(t, err, "Foobar")
|
||||
assert.Zero(t, resultCounter)
|
||||
})
|
||||
@@ -219,7 +220,7 @@ func TestMap(t *testing.T) {
|
||||
resultCounter++
|
||||
return errors.New("Foobar")
|
||||
}
|
||||
err := orderedmap.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
|
||||
err := datamodel.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
|
||||
require.ErrorContains(t, err, "Foobar")
|
||||
assert.Equal(t, 1, resultCounter)
|
||||
})
|
||||
@@ -238,7 +239,7 @@ func TestMap(t *testing.T) {
|
||||
resultCounter++
|
||||
return nil
|
||||
}
|
||||
err := orderedmap.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
|
||||
err := datamodel.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, resultCounter)
|
||||
})
|
||||
@@ -257,7 +258,7 @@ func TestMap(t *testing.T) {
|
||||
resultCounter++
|
||||
return io.EOF
|
||||
}
|
||||
err := orderedmap.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
|
||||
err := datamodel.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, resultCounter)
|
||||
})
|
||||
@@ -298,7 +299,8 @@ func TestFirst(t *testing.T) {
|
||||
|
||||
func TestLen(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
require.Zero(t, orderedmap.Len(nil))
|
||||
m := (*orderedmap.Map[string, int])(nil)
|
||||
require.Zero(t, orderedmap.Len(m))
|
||||
})
|
||||
|
||||
t.Run("Single item", func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user