Skip to content

Commit

Permalink
docs: enum
Browse files Browse the repository at this point in the history
  • Loading branch information
jmattheis committed Feb 22, 2024
1 parent 0a36225 commit 790e21f
Show file tree
Hide file tree
Showing 40 changed files with 985 additions and 163 deletions.
135 changes: 57 additions & 78 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ do is create an interface and execute goverter. The project is meant as
alternative to [jinzhu/copier](https://github.com/jinzhu/copier) that doesn't
use reflection.

[Getting Started](https://goverter.jmattheis.de/guide/getting-started)
[Installation](https://goverter.jmattheis.de/guide/install)
[CLI](https://goverter.jmattheis.de/reference/cli)
[Config](https://goverter.jmattheis.de/reference/settings)
Expand All @@ -36,6 +37,7 @@ use reflection.
- **Fast execution**: No reflection is used at runtime
- Automatically converts builtin types: slices, maps, named types, primitive
types, pointers, structs with same fields
- [Enum support](https://goverter.jmattheis.de/guide/enum)
- [Deep copies](https://en.wikipedia.org/wiki/Object_copying#Deep_copy) per
default and supports [shallow
copying](https://en.wikipedia.org/wiki/Object_copying#Shallow_copy)
Expand All @@ -46,87 +48,64 @@ use reflection.
- Detailed [documentation](https://goverter.jmattheis.de/) with a lot examples
- Thoroughly tested, see [our test scenarios](./scenario)

## Getting Started

1. Ensure your `go version` is 1.16 or above

1. Create a go modules project if you haven't done so already

```bash
$ go mod init module-name
```

1. Create your converter interface and mark it with a comment containing
[`goverter:converter`](https://goverter.jmattheis.de/reference/converter)

`input.go`

```go
package example
// goverter:converter
type Converter interface {
ConvertItems(source []Input) []Output
// goverter:ignore Irrelevant
// goverter:map Nested.AgeInYears Age
Convert(source Input) Output
}
type Input struct {
Name string
Nested InputNested
}
type InputNested struct {
AgeInYears int
}
type Output struct {
Name string
Age int
Irrelevant bool
}
```

See [Settings](https://goverter.jmattheis.de/reference/settings) for more information.

1. Run `goverter`:

```bash
$ go run github.com/jmattheis/goverter/cmd/goverter@latest gen ./
```

It's recommended to use an explicit version instead of `latest`. See
[Installation](https://goverter.jmattheis.de/guide/install) and
[CLI](https://goverter.jmattheis.de/reference/cli) for more information.
1. goverter created a file at `./generated/generated.go`, it may look like this:
```go
package generated
import example "goverter/example"
type ConverterImpl struct{}
func (c *ConverterImpl) Convert(source example.Input) example.Output {
var exampleOutput example.Output
exampleOutput.Name = source.Name
exampleOutput.Age = source.Nested.AgeInYears
return exampleOutput
}
func (c *ConverterImpl) ConvertItems(source []example.Input) []example.Output {
var exampleOutputList []example.Output
if source != nil {
exampleOutputList = make([]example.Output, len(source))
for i := 0; i < len(source); i++ {
exampleOutputList[i] = c.Convert(source[i])
}
## Example

Given this converter:

```go
package example

// goverter:converter
type Converter interface {
ConvertItems(source []Input) []Output

// goverter:ignore Irrelevant
// goverter:map Nested.AgeInYears Age
Convert(source Input) Output
}

type Input struct {
Name string
Nested InputNested
}
type InputNested struct {
AgeInYears int
}
type Output struct {
Name string
Age int
Irrelevant bool
}
```

Goverter will generated these conversion methods:

```go
package generated

import example "goverter/example"

type ConverterImpl struct{}

func (c *ConverterImpl) Convert(source example.Input) example.Output {
var exampleOutput example.Output
exampleOutput.Name = source.Name
exampleOutput.Age = source.Nested.AgeInYears
return exampleOutput
}
func (c *ConverterImpl) ConvertItems(source []example.Input) []example.Output {
var exampleOutputList []example.Output
if source != nil {
exampleOutputList = make([]example.Output, len(source))
for i := 0; i < len(source); i++ {
exampleOutputList[i] = c.Convert(source[i])
}
return exampleOutputList
}
```
return exampleOutputList
}
```

See [Generation](https://goverter.jmattheis.de/explanation/generation) for more information.
See [Getting Started](https://goverter.jmattheis.de/guide/getting-started).

## Versioning

Expand Down
9 changes: 6 additions & 3 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,19 @@ export default defineConfig({
},
},
nav: [
{ text: "Getting Started", link: "/" },
{ text: "Getting Started", link: "/guide/getting-started" },
{ text: "Settings", link: "/reference/settings" },
{ text: "FAQ", link: "/faq" },
{ text: "Changelog", link: "/changelog" },
],
sidebar: [
{ text: "Intro", link: "/" },
{
text: "Guides",
items: [
{ text: "Getting Started", link: "/" },
{ text: "Getting Started", link: "/guide/getting-started" },
{ text: "Installation", link: "/guide/install" },
{ text: "Error early", link: "/guide/error-early" },
{ text: "Convert Enums", link: "/guide/enum" },
{
text: "Output into same package",
link: "/guide/output-same-package",
Expand All @@ -72,6 +74,7 @@ export default defineConfig({
},
{ text: "Define Settings", link: "/reference/define-settings" },
{ text: "Settings Overview", link: "/reference/settings" },
{ text: "Enums", link: "/reference/enum" },
{
text: "Converter",
collapsed: true,
Expand Down
7 changes: 7 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import GH from './GH.vue';
- Require go1.18 for building Goverter
- Add current working directory `-cwd` option to [CLI](./reference/cli.md)
- Add [`wrapErrorsUsing`](./reference/wrapErrorsUsing.md)
- Add [`enum`](./reference/enum.md), See [Guide: Enums](guide/enum.md)
- Fix error messages when there is an return error mismatch

_internals_:

- Require the examples to be up-to-date via CI
- Fix file permissions in tests

## v1.3.2

Expand Down
132 changes: 132 additions & 0 deletions docs/guide/enum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# How to convert enums

[[toc]]

## Definition

The Go language doesn't explicit define enums. In the context of Goverter an
enum type is defined as a named type with an [underlying
type](https://go.dev/ref/spec#Underlying_types) of (`float`, `string`, or
`integer`) having at least one constant defined in the same package.

These examples would qualify as enums:

::: code-group
<<< @../../example/enum/unknown/input/enum.go [iota.go]
<<< @../../example/enum/unknown/output/enum.go [string.go]
:::

## Conversion

When goverter sees types which both qualify as enum according to the definition
above then it tries to convert each key of the source enum to the target enum.
When not all keys of the source enum can be mapped, goverter will error.

## Unknown enum values

In Go it's possible to manually instantiate values of types qualifying as
[Enum](#definition). E.g.

```go
func GetColor() Color { return Color("invalid") }

type Color string
const Green Color = "green"
const Blue Color = "blue"
```

it's not clear how to handle the `Color("invalid")`, therefore _you_ have to
explicitly configure how to convert an invalid enum value. You can do this by
configuring [`enum:unknown`](../reference/enum.md#enum-unknown-action) with one
of these actions.

`enum:unknown @error`: return an error when an invalid value is encountered.

::: details Example (click me)
::: code-group
<<< @../../example/enum/unknown/error/input.go
<<< @../../example/enum/unknown/input/enum.go [input/enum.go]
<<< @../../example/enum/unknown/output/enum.go [output/enum.go]
<<< @../../example/enum/unknown/error/generated/generated.go [generated/generated.go]
:::

`enum:unknown @ignore`: ignore invalid values and return the zero value of the enum type.

::: details Example (click me)
::: code-group
<<< @../../example/enum/unknown/ignore/input.go
<<< @../../example/enum/unknown/input/enum.go [input/enum.go]
<<< @../../example/enum/unknown/output/enum.go [output/enum.go]
<<< @../../example/enum/unknown/ignore/generated/generated.go [generated/generated.go]
:::

`enum:unknown @panic`: panic when an invalid value is encountered

::: details Example (click me)
::: code-group
<<< @../../example/enum/unknown/panic/input.go
<<< @../../example/enum/unknown/input/enum.go [input/enum.go]
<<< @../../example/enum/unknown/output/enum.go [output/enum.go]
<<< @../../example/enum/unknown/panic/generated/generated.go [generated/generated.go]
:::

`enum:unknown KEY`: use an enum key when an invalid value in encountered

::: details Example (click me)
::: code-group
<<< @../../example/enum/unknown/key/input.go
<<< @../../example/enum/unknown/key/input/enum.go [input/enum.go]
<<< @../../example/enum/unknown/key/output/enum.go [output/enum.go]
<<< @../../example/enum/unknown/key/generated/generated.go [generated/generated.go]
:::

## Mapping enum keys

If your source and target enum have differently named keys, you can use
[`enum:map`](../reference/enum.md#enummap-source-target) to define the mapping.

::: details Example (click me)
::: code-group
<<< @../../example/enum/map/input.go
<<< @../../example/enum/map/input/enum.go [input/enum.go]
<<< @../../example/enum/map/output/enum.go [output/enum.go]
<<< @../../example/enum/map/generated/generated.go [generated/generated.go]
:::

You can also use any of the `@actions` from [`enum:unknown`](#unknown-enum-values). E.g.

::: details Example (click me)
::: code-group
<<< @../../example/enum/map-panic/input.go
<<< @../../example/enum/map-panic/input/enum.go [input/enum.go]
<<< @../../example/enum/map-panic/output/enum.go [output/enum.go]
<<< @../../example/enum/map-panic/generated/generated.go [generated/generated.go]
:::

If you have keys that differ in the same format you can use the transformer
[`enum:transform regex`](../reference/enum.md#enum-transform-regex-search-replace)
to do a regex search and replace.

::: details Example (click me)
::: code-group
<<< @../../example/enum/transform-regex/input.go
<<< @../../example/enum/transform-regex/generated/generated.go [generated/generated.go]
:::

If that's still not powerful enough for you, then you can define [a custom enum
transformer](../reference/enum.md#enum-transform-custom)

## Disable enum detection and conversion

To disable enum detection and conversion, add `enum no` to the converter.

::: details Example (click me)
::: code-group
<<< @../../example/enum/disable/input.go
<<< @../../example/enum/disable/generated/generated.go [generated/generated.go]
:::

## Handle false positives

You can exclude wrongly detected enums via
[`enum:exclude`](../reference/enum.md#enum-exclude).
Loading

0 comments on commit 790e21f

Please sign in to comment.