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

[FEATURE] Support marshal / unmarshal enum values to target types in Go generator #2043

Open
2 tasks done
condorcet opened this issue Jun 16, 2024 · 7 comments
Open
2 tasks done
Labels
enhancement New feature or request stale

Comments

@condorcet
Copy link

condorcet commented Jun 16, 2024

Why do we need this improvement?

For now, generated enums in Go generator represents as uint https://github.com/asyncapi/modelina/blob/master/src/generators/go/renderers/EnumRenderer.ts#L28

This also covered by docstring here

  • This renderer is a generic solution that works for all types of enum values.
  • This is also why you wont see `type MyEnum string´ even if possible.

For example, if we have string type enum like this

    myObject:
      type: object
      properties:
        id:
          type: string
        status:
          $id: status
          type: string
          enum:
            - created
            - updated
            - deleted

Generated code will be:

// Status represents an enum of Status.
type Status uint

const (
	StatusCreated Status = iota
	StatusUpdated
	StatusDeleted
)

// Value returns the value of the enum.
func (op Status) Value() any {
	if op >= Status(len(StatusValues)) {
		return nil
	}
	return StatusValues[op]
}

var StatusValues = []any{"created", "updated", "deleted"}
var ValuesToStatus = map[any]Status{
	StatusValues[StatusCreated]: StatusCreated,
	StatusValues[StatusUpdated]: StatusUpdated,
	StatusValues[StatusDeleted]: StatusDeleted,
}

It becomes blocker to use generated type "as is", because when we need marshal / unmarshal this type to JSON, we can't rely on string type (underlying Status type is uint).

data, _ := json.Marshal(&MyObject{
    Status: StatusUpdated, // will be uint, not a string
})

Of course, we have Value() method to use actual values of enum, but it forces to make some additional wrappers on original type. We can't make something like this:

data, _ := json.Marshal(&MyObject{
    Status: StatusUpdated.Value(), // type mismatch
})

How will this change help?

This change unlocks using of generated enum types "as is" with different types (e.g. string) to satisfy original JSON schema.

Screenshots

No response

How could it be implemented/designed?

One of the solution is to add methods MarshalText / UnmarshalText to generated type.
It helps json codec to serialize data to target type according JSON schema.

In scope of the example very simplified solution for string enum values may look this:

func (op Status) MarshalText() ([]byte, error) {
	val := op.Value()
	if val == nil {
		return nil, fmt.Errorf("unknown enum value %d", op)
	}
        
        // only string type supported for simplicity of the example
	strVal, ok := val.(string)
	if !ok {
		return nil, fmt.Errorf("unexpected enum value type %T", val)
	}
	return []byte(strVal), nil
}

and

func (op *Status) UnmarshalText(text []byte) error {
	key := string(text)
	val, ok := ValuesToStatus[key]
	if !ok {
		val = ValuesToStatus[StatusValues[0]] // using first value as "default". We need to think is it correct to support such semantics?
	}
	*op = val
	return nil
}

This solution aims not to change internal representation of enum value, but only JSON serialization. So, this change can be considered backward compatible.

🚧 Breaking changes

No

👀 Have you checked for similar open issues?

  • I checked and didn't find a similar issue

🏢 Have you read the Contributing Guidelines?

Are you willing to work on this issue?

None

@condorcet condorcet added the enhancement New feature or request label Jun 16, 2024
Copy link
Contributor

Welcome to AsyncAPI. Thanks a lot for reporting your first issue. Please check out our contributors guide and the instructions about a basic recommended setup useful for opening a pull request.
Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out this issue.

@kavania2002
Copy link

@condorcet
I guess you can use Status.Value(StatusUpdated) which would return updated as a string.

@jonaslagoni
Copy link
Member

Have you tried using the next branch? I know there are some Go improvements coming there, but don't recall if this is fixed ✌️

@condorcet
Copy link
Author

@condorcet
I guess you can use Status.Value(StatusUpdated) which would return updated as a string.

Yes, it works, but as I mentioned before it's impossible to marshal type "as is", because underlying type is uint.

For example, we can't do something like that

// Status represents an enum of Status.
type Status uint

const (
	StatusCreated Status = iota
	StatusUpdated
	StatusDeleted
)

type MyObject struct {
    Status Status
}

data, _ := json.Marshal(&MyObject{
    Status: StatusUpdated.Value(), // type mismatch, Value returns any, but type is uint
})

@condorcet
Copy link
Author

Have you tried using the next branch? I know there are some Go improvements coming there, but don't recall if this is fixed ✌️

@jonaslagoni
Yes, I've tried, the problem still exists.

For my project I extend generator with MarshalJSON / UnmarshalJSON. Maybe I can make PR with proposal?

@jonaslagoni
Copy link
Member

Please do yea ✌️ Make sure you target next 🙂

Copy link
Contributor

This issue has been automatically marked as stale because it has not had recent activity 😴

It will be closed in 120 days if no further activity occurs. To unstale this issue, add a comment with a detailed explanation.

There can be many reasons why some specific issue has no activity. The most probable cause is lack of time, not lack of interest. AsyncAPI Initiative is a Linux Foundation project not owned by a single for-profit company. It is a community-driven initiative ruled under open governance model.

Let us figure out together how to push this issue forward. Connect with us through one of many communication channels we established here.

Thank you for your patience ❤️

@github-actions github-actions bot added the stale label Oct 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request stale
Projects
None yet
Development

No branches or pull requests

3 participants