Skip to content

fix: set pointer as a 3.1 reference type - #838

Open
dylanhitt wants to merge 1 commit into
mainfrom
fix/ptr-is-ref
Open

fix: set pointer as a 3.1 reference type#838
dylanhitt wants to merge 1 commit into
mainfrom
fix/ptr-is-ref

Conversation

@dylanhitt

Copy link
Copy Markdown
Collaborator

Closes #810

@bwees

I believe this will solve your issue

@bwees

bwees commented Jul 28, 2026

Copy link
Copy Markdown

It does solve the issue but also introduces a regression. Nullability should be scoped to the user of referenced types and not drill down into the type. For example:

package main

import (
	"encoding/json"
	"fmt"

	"github.com/go-fuego/fuego"
)

type Owner struct {
	Name string `json:"name"`
}

type Widget struct {
	Name  *string  `json:"name"`  // pointer -> nullable scalar
	Tags  []string `json:"tags"`  // slice
	Owner *Owner   `json:"owner"` // pointer to a named struct -> $ref
}

func main() {
	s := fuego.NewServer()

	fuego.Get(s, "/widget", func(c fuego.ContextNoBody) (Widget, error) {
		return Widget{}, nil
	})

	// Owner is also returned on its own, where it is never null.
	fuego.Get(s, "/owner", func(c fuego.ContextNoBody) (Owner, error) {
		return Owner{}, nil
	})

	doc := s.OpenAPI.Description()
	fmt.Println("openapi:", doc.OpenAPI)
	for _, name := range []string{"Widget", "Owner"} {
		out, _ := json.MarshalIndent(doc.Components.Schemas[name].Value, "", "  ")
		fmt.Printf("%s: %s\n", name, out)
	}
}

now produces:

"Owner": {
         "properties": {
           "name": {
             "type": "string"
           }
         },
         "required": [
           "name"
         ],
-        "type": "object"
+        "type": [
+          "object",
+          "null"
+        ]
       },
       "Widget": {
         "description": "Widget schema",
         "properties": {
           "name": {
-            "nullable": true,
-            "type": "string"
+            "type": [
+              "string",
+              "null"
+            ]
           },
           "owner": {
             "$ref": "#/components/schemas/Owner"
           },
           "tags": {
             "items": {
               "type": "string"
             },
             "type": [
               "array",
               "null"
             ]
           }
         },
         "required": [
           "name",
           "owner",
           "tags"
         ],
         "type": "object"
       },

Note the addition of null to the Owner type. This messes up other endpoints that reference Owner non-nullable.

@dylanhitt

dylanhitt commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator Author

Ah, I see. I'm not sure this is gonna be possible to do without having to fight with kin's implementation as it doesn't seem 3.1 is completely supported either... 🤔

@bwees

bwees commented Jul 28, 2026

Copy link
Copy Markdown

Interesting, I had Claude build this shim to unblock us. It seems to solve the issues we were seeing with our generator.

func fixNullable(doc *openapi3.T) {
	if doc == nil {
		return
	}

	var walk func(*openapi3.SchemaRef)
	walk = func(ref *openapi3.SchemaRef) {
		if ref == nil || ref.Ref != "" || ref.Value == nil {
			return
		}
		s := ref.Value

		if s.Nullable && s.Type != nil && !s.Type.Includes("null") {
			*s.Type = append(*s.Type, "null")
			s.Nullable = false
		}

		for _, p := range s.Properties {
			walk(p)
		}
		walk(s.Items)
		if s.AdditionalProperties.Schema != nil {
			walk(s.AdditionalProperties.Schema)
		}
	}

	for _, ref := range schemaRoots(doc) {
		walk(ref)
	}
}

then is called fixNullable(router.OpenAPI.Description()). Its an ugly hack but not sure if duego has some form of post processing steps that could be added until kin is patched

@dylanhitt

Copy link
Copy Markdown
Collaborator Author

Right, I can't see a way of doing this at generation time. Let me read through some kin stuff and see where they are at. In the meantime, some post processing thing would be nice, and makes it conceptually easier to work with specs after output than having to use schemacustomizer

func WithOpenAPIPostProcessor(myfunc []func (openAPI *OpenAPI)) EngineOption 

Could be helpful in this case.

@bwees

bwees commented Jul 28, 2026

Copy link
Copy Markdown

Yeah I have quite a few shims to get the spec into a format I want (mostly to clean up generator output/behavior and to match my old NestJS implementation) including non nullable arrays, content negotiation parameters, promoting enum structs that I have, and setting binary payloads to the correct type. A build in post processing driver would be extremely useful in this case

@dylanhitt

Copy link
Copy Markdown
Collaborator Author

I think I'm gonna close this. Let's leave the issue open though. Looking at kin it may not be that hard to get their output doing this or maybe provide an option to do it.

@dylanhitt

Copy link
Copy Markdown
Collaborator Author

@bwees I looked into kin-openapi some more. They don't seem to have move their openapigen package over yet. They do also seem to want to support both 3.0 and 3.1. I'm probably gonna look into doing it for them, but it may take bit cause I'm not sure what they would want a solution to look like.

What your thoughts on this.

fuego.WithEngineOptions(
			fuego.WithWalkSchemas(func(_ string, ref *openapi3.SchemaRef) error {
				schema := ref.Value
				if schema.Nullable && schema.Type != nil && !schema.Type.Includes("null") {
					*schema.Type = append(*schema.Type, "null")
					schema.Nullable = false
				}
				return nil
			}),
		),

i'll probably keep the engine option or it's probably better as an openapi option to walk. I can then export a func called something OpenAPI31Nullable which you can then just use. It can probably lead into the contribution of more generic post processing options.

@dylanhitt

Copy link
Copy Markdown
Collaborator Author

Found myself adding this in for my day job today, for probably a similar reason to yours

s.OpenAPI.Description().WalkSchemas(func(_ string, ref *openapi3.SchemaRef) error {
		schema := ref.Value
		if schema.Nullable && schema.Type != nil && !schema.Type.Includes("null") {
			*schema.Type = append(*schema.Type, "null")
			schema.Nullable = false
		}
		return nil
	})

	if onlySpec {
		s.OutputOpenAPISpec()
		return nil
	}

I think an option would be pretty useful.

@bwees

bwees commented Jul 31, 2026

Copy link
Copy Markdown

agreed, I like this

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fuego generates nullable: true on OAPI 3.1 specs

2 participants