Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide datastructures that retain the original order of items in the document #119

Closed
TristanSpeakEasy opened this issue May 19, 2023 · 6 comments
Labels
enhancement New feature or request

Comments

@TristanSpeakEasy
Copy link
Contributor

Ideally we would be able to iterate through a document in the order of items (think paths, operations etc, etc) but due to the use of go native maps this isn't possible with the high level API.

I would recommended switching to using https://github.com/wk8/go-ordered-map or similar to provide this functionality which might also help with this other issue I opened #109 to ensure rendering new items out can retain an order.

@daveshanley
Copy link
Member

This is a good idea; it's a heavy lift, however. A good v0.2.0 feature I think.

@diamondburned
Copy link

Seeing how we want to preserve API backwards compatibility, is there a chance that such a feature would be merged in, or would we have to wait until at least v0.2?

@diamondburned
Copy link

diamondburned commented Aug 6, 2023

I was half way through implementing this when I noticed that I don't really need any of this to preserve object property order. All I needed to do was use the GoLow() method and yoink the line number info:

propertyLines := make(map[string]int, len(propertyNames))
for _, name := range propertyNames {
    lowProperty := schema.GoLow().FindProperty(name)
    propertyLines[name] = lowProperty.ValueNode.Line
}
sort.Slice(propertyNames, func(i, j int) bool {
    return propertyLines[propertyNames[i]] < propertyLines[propertyNames[j]]
})

Update: this works better:

propertyLines := make(map[string]int, len(propertyNames))
for _, name := range propertyNames {
    lowProperty := schema.Properties[name].GoLow()
    propKeyNode := darkfuckingmagic.UnexportedField[*yaml.Node](lowProperty, "kn")
    propertyLines[name] = propKeyNode.Line
}
sort.Slice(propertyNames, func(i, j int) bool {
    return propertyLines[propertyNames[i]] < propertyLines[propertyNames[j]]
})

@daveshanley
Copy link
Member

daveshanley commented Aug 6, 2023

We have this feature being cooked up in #138 and #148.

@TristanSpeakEasy
Copy link
Contributor Author

TristanSpeakEasy commented Nov 5, 2023

We solved this in the interim with:

func SortMap[hV any, lV any](highMap map[string]hV, lowMap map[low.KeyReference[string]]lV) *orderedmap.OrderedMap[string, hV] {
	if highMap == nil {
		return nil
	}

	if len(lowMap) == 0 {
		return utils.MapToOrderedMap(highMap)
	}

	type val struct {
		key low.KeyReference[string]
		val hV
	}

	var vals []val

	for k := range lowMap {
		vals = append(vals, val{key: k, val: highMap[k.Value]})
	}

	sort.Slice(vals, func(i, j int) bool {
		return vals[i].key.GetKeyNode().Line < vals[j].key.GetKeyNode().Line
	})

	om := orderedmap.New[string, hV]()
	for _, v := range vals {
		om.Set(v.key.Value, v.val)
	}

	return om
}

and

func GetSortedOperations(p *v3.PathItem) *orderedmap.OrderedMap[string, *v3.Operation] {
	type val struct {
		key  string
		val  *v3.Operation
		line int
	}

	var vals []val

	if p.Get != nil {
		vals = append(vals, val{key: v3Low.GetLabel, val: p.Get, line: p.GoLow().Get.KeyNode.Line})
	}
	if p.Put != nil {
		vals = append(vals, val{key: v3Low.PutLabel, val: p.Put, line: p.GoLow().Put.KeyNode.Line})
	}
	if p.Post != nil {
		vals = append(vals, val{key: v3Low.PostLabel, val: p.Post, line: p.GoLow().Post.KeyNode.Line})
	}
	if p.Delete != nil {
		vals = append(vals, val{key: v3Low.DeleteLabel, val: p.Delete, line: p.GoLow().Delete.KeyNode.Line})
	}
	if p.Options != nil {
		vals = append(vals, val{key: v3Low.OptionsLabel, val: p.Options, line: p.GoLow().Options.KeyNode.Line})
	}
	if p.Head != nil {
		vals = append(vals, val{key: v3Low.HeadLabel, val: p.Head, line: p.GoLow().Head.KeyNode.Line})
	}
	if p.Patch != nil {
		vals = append(vals, val{key: v3Low.PatchLabel, val: p.Patch, line: p.GoLow().Patch.KeyNode.Line})
	}
	if p.Trace != nil {
		vals = append(vals, val{key: v3Low.TraceLabel, val: p.Trace, line: p.GoLow().Trace.KeyNode.Line})
	}

	
	sort.Slice(vals, func(i, j int) bool {
		return vals[i].key < vals[j].key
	})
	

	om := orderedmap.New[string, *v3.Operation]()

	for _, v := range vals {
		om.Set(v.key, v.val)
	}

	return om
}

@daveshanley might be worth providing helper methods that do this as part of libopenapi until those other changes are ready?

as the code to call these is not great:

if schema.Properties != nil {
		if schema.GoLow() != nil {
			s.Properties = SortMap(schema.Properties, schema.GoLow().Properties.Value)
		} else {
			s.Properties = SortMap[*base.SchemaProxy, any](schema.Properties, nil)
		}
	}

would be much nicer to call schema.GetOrderedProperties() or similar (We fall back to alphabetically ordered if the schema model is not backed by a yaml/json document)

Though would only be worth doing if those other fixes are still a ways out?

@daveshanley
Copy link
Member

Resolved in v0.14

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants