Skip to content

additionalOperations is parsed into the low-level model but never reaches the high-level model #628

Description

@sheidkamp

Summary

An OpenAPI 3.2 document with additionalOperations loses those operations on a parse-and-render round trip. The low-level model parses them, but the high-level PathItem.AdditionalOperations stays nil, GetOperations() omits them, and they are absent from rendered output.

Reproduction

mkdir /tmp/libopenapi-repro && cd /tmp/libopenapi-repro
go mod init repro
go get github.com/pb33f/libopenapi@v0.38.7

cat > main.go <<'EOF'
package main

import (
	"encoding/json"
	"fmt"

	"github.com/pb33f/libopenapi"
)

const spec = `{
  "openapi": "3.2.0",
  "info": {"title": "Repro", "version": "1.0.0"},
  "paths": {"/cache": {
    "get": {"summary": "Read the cache"},
    "additionalOperations": {"PURGE": {"summary": "Purge the cache"}}
  }}
}`

func cacheAdditionalOps(doc []byte) string {
	var d struct {
		Paths map[string]map[string]json.RawMessage `json:"paths"`
	}
	json.Unmarshal(doc, &d)
	if block, ok := d.Paths["/cache"]["additionalOperations"]; ok {
		return string(block)
	}
	return "(absent)"
}

func main() {
	doc, _ := libopenapi.NewDocument([]byte(spec))
	model, _ := doc.BuildV3Model()
	item, _ := model.Model.Paths.PathItems.Get("/cache")

	// The low-level model parsed the additional operations.
	fmt.Println("low-level model additionalOperations:")
	for pair := item.GoLow().AdditionalOperations.Value.First(); pair != nil; pair = pair.Next() {
		fmt.Printf("  %s: %q\n", pair.Key().Value, pair.Value().Value.Summary.Value)
	}

	// The high-level model never received them.
	fmt.Println("\nhigh-level model:")
	fmt.Print("  AdditionalOperations:")
	if item.AdditionalOperations == nil {
		fmt.Print(" nil")
	}
	for pair := item.AdditionalOperations.First(); pair != nil; pair = pair.Next() {
		fmt.Printf(" %s", pair.Key())
	}
	fmt.Println()
	fmt.Print("  Operations returned by GetOperations():")
	for pair := item.GetOperations().First(); pair != nil; pair = pair.Next() {
		fmt.Printf(" %s", pair.Key())
	}
	fmt.Println()

	rendered, _ := model.Model.RenderJSON("  ")
	fmt.Printf("\nrendered document:\n%s\n", rendered)

	// What /cache was in the origina doc, against what it rendered back out.
	fmt.Printf("\nexpected additionalOperations: %s\n", cacheAdditionalOps([]byte(spec)))
	fmt.Printf("actual   additionalOperations: %s\n", cacheAdditionalOps(rendered))
}
EOF

go run .

Output — PURGE is parsed, then silently dropped:

low-level model additionalOperations:
  PURGE: "Purge the cache"

high-level model:
  AdditionalOperations: nil
  Operations returned by GetOperations(): get

rendered document:
{
  "openapi": "3.2.0",
  "info": {
    "title": "Repro",
    "version": "1.0.0"
  },
  "paths": {
    "/cache": {
      "get": {
        "summary": "Read the cache"
      }
    }
  }
}

expected additionalOperations: {"PURGE": {"summary": "Purge the cache"}}
actual   additionalOperations: (absent)

The PURGE operation is gone from the rendered document, and no error or warning is produced. Replacing the additionalOperations container with a bare "PURGE": {...} key on the path item gives the same result.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions