Skip to content

oneOf in nested RequestBody structure results in empty json #1882

@mminklet

Description

@mminklet

I believe this is similar to issue #970 but I can't be sure.

We have the following structure (some things have been removed for brevity):

openapi: 3.0.0
info:
  title: WhatsApp API
  description: >-
    Internal API for WhatsApp.
  version: 1.0.0
  x-go-package: "github.com/oapi-codegen/runtime"
  paths:
  /v2/whatsapp:
    post:
      summary: Send a single WhatsApp message.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                content_type:
                  type: string
                  description: Type of the message content (e.g., template).
                  enum: [template]
                content:
                  type: object
                  description: Content details for the message.
                  oneOf:
                    - $ref: '#/components/schemas/TemplateContent'
                  discriminator:
                    propertyName: object_type
              required:
                - content_type
                - content
            example:
              content_type: "template"
              content:
                template:
                  name: "welcome_new_customer"
                  parameters: ["Joe"]
                  
    TemplateContent:
      type: object
      properties:
        object_type:
          type: string
        template:
          type: object
          description: Template details for the message content.
          properties:
            name:
              type: string
              description: Name of the template used. Must be in lower case, alphanumeric characters, and underscore only.
            parameters:
              type: array
              description: Values to be populated in the template.
              items:
                type: string
          required:
            - name

When unmarshalling the content inside the request body, it is empty. The patch in #970 is for responses only, I can have a go at the templating in a similar patch style as in that thread, but wanted to raise this bug as related

I attempted to manually fix this via creating what I think are the methods that are intended to be generated, based on the documentation, but I would like to confirm that before submitting any kind of patch

Generated successfully:

// TemplateContent defines model for TemplateContent.
type TemplateContent struct {
	ObjectType string `json:"object_type"`

	// Template Template details for the message content.
	Template *struct {
		// Name Name of the template used. Must be in lower case, alphanumeric characters, and underscore only.
		Name string `json:"name"`

		// Parameters Values to be populated in the template.
		Parameters *[]string `json:"parameters,omitempty"`
	} `json:"template,omitempty"`
}


// PostV2FoobarJSONBody defines parameters for PostV2Foobar.
type PostV2FoobarJSONBody struct {
	// Content Content details for the message.
	Content PostV2FoobarJSONBody_Content `json:"content"`

	// ContentType Type of the message content (e.g., template).
	ContentType PostV2FoobarJSONBodyContentType `json:"content_type"`

	// From Registered Foobar sender number in international format.
	From string `json:"from"`

	// MessageRef Optional reference ID set by the user for the message.
	MessageRef *string `json:"message_ref,omitempty"`

	// To Recipient phone number in international format.
	To string `json:"to"`
}

// PostV2FoobarJSONBody_Content defines parameters for PostV2Foobar.
type PostV2FoobarJSONBody_Content struct {
	union json.RawMessage
}

// PostV2FoobarJSONBodyContentType defines parameters for PostV2Foobar.
type PostV2FoobarJSONBodyContentType string

// PostV2FoobarJSONRequestBody defines body for PostV2Foobar for application/json ContentType.
type PostV2FoobarJSONRequestBody PostV2FoobarJSONBody

Additional methods manually added

// AsTemplateContent returns the union data inside the PostV2FoobarJSONBody_Content as a TemplateContent
func (t PostV2FoobarJSONBody_Content) AsTemplateContent() (TemplateContent, error) {
	var body TemplateContent
	err := json.Unmarshal(t.union, &body)
	return body, err
}

// FromTemplateContent overwrites any union data inside the PostV2FoobarJSONBody_Content as the provided TemplateContent
func (t *PostV2FoobarJSONBody_Content) FromTemplateContent(v TemplateContent) error {
	b, err := json.Marshal(v)
	t.union = b
	return err
}

// MergeTemplateContent performs a merge with any union data inside the PostV2FoobarJSONBody_Content, using the provided TemplateContent
func (t *PostV2FoobarJSONBody_Content) MergeTemplateContent(v TemplateContent) error {
	b, err := json.Marshal(v)
	if err != nil {
		return err
	}

	merged, err := runtime.JSONMerge(t.union, b)
	t.union = merged
	return err
}

func (t PostV2FoobarJSONBody_Content) MarshalJSON() ([]byte, error) {
	b, err := t.union.MarshalJSON()
	return b, err
}

func (t *PostV2FoobarJSONBody_Content) UnmarshalJSON(b []byte) error {
	err := t.union.UnmarshalJSON(b)
	return err
}

I then used it like so (added a Discriminator)

bodyContent, err := body.Content.ValueByDiscriminator()
	if err != nil {
		return nil, err
	}
	discriminator, err := body.Content.Discriminator()

	// This seems long winded but if we want to use it, it needs casting to the correct type, and if we want it adding to the full bodyRequest, then marshalling into the 'union' type. 
	if discriminator == "TemplateContent" {
		v, ok := bodyContent.(openapi.TemplateContent)
		if !ok {
			panic("oops")
		}
		err = body.Content.FromTemplateContent(v)
		if err != nil {
			return nil, fmt.Errorf("failed to merge TemplateContent to body content: %w", err)
		}
	}
	if discriminator == "FoobarContent" {
		v, ok := bodyContent.(openapi.FoobarContent)
		if !ok {
			panic("oops")
		}
		err = body.Content.FromFoobarContent(v)
		if err != nil {
			return nil, fmt.Errorf("failed to merge FoobarContant to body content: %w", err)
		}
	}

So two questions, is the missing methods a bug, and which template should I concentrate on for a patch?

and

When the methods are in place, is that the right way to use it? It's a bit long winded, which is fine but want to be sure.

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