Skip to content

Commit

Permalink
feat: add context
Browse files Browse the repository at this point in the history
  • Loading branch information
jmattheis committed Dec 13, 2024
1 parent d9dfc2b commit a59de09
Show file tree
Hide file tree
Showing 33 changed files with 205 additions and 49 deletions.
2 changes: 1 addition & 1 deletion builder/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ func mapField(
Params: method.ParamsNone,
ContextMatch: config.StructMethodContextRegex,
CustomCall: nextIDCode,
})
}, method.EmptyLocalOpts)
if err != nil {
return nil, nil, nil, nil, false, NewError(err.Error()).Lift(lift...)
}
Expand Down
8 changes: 3 additions & 5 deletions config/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"go/types"
"path/filepath"
"regexp"
"strings"

"github.com/jmattheis/goverter/config/parse"
Expand All @@ -26,8 +25,7 @@ const (
)

var DefaultCommon = Common{
Enum: enum.Config{Enabled: true},
ArgContextRegex: regexp.MustCompile("^ctx|^context"),
Enum: enum.Config{Enabled: true},
}

var DefaultConfigInterface = ConverterConfig{
Expand Down Expand Up @@ -124,12 +122,12 @@ func initConverter(loader *pkgload.PackageLoader, rawConverter *RawConverter) (*

if rawConverter.InterfaceName != "" {
c.ConverterConfig = DefaultConfigInterface
v, err := loader.GetOneRaw(c.Package, rawConverter.InterfaceName)
_, interfaceObj, err := loader.GetOneRaw(c.Package, rawConverter.InterfaceName)
if err != nil {
return nil, err
}

c.typ = v.Type()
c.typ = interfaceObj.Type()
c.Name = rawConverter.InterfaceName + "Impl"
return c, nil
}
Expand Down
12 changes: 9 additions & 3 deletions config/method.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Method struct {

Location string
updateParam string
localOpts method.LocalOpts
}

type FieldMapping struct {
Expand Down Expand Up @@ -61,11 +62,11 @@ func parseMethods(ctx *context, rawConverter *RawConverter, c *Converter) error
return nil
}
for name, lines := range rawConverter.Methods {
fun, err := ctx.Loader.GetOneRaw(c.Package, name)
_, fn, err := ctx.Loader.GetOneRaw(c.Package, name)
if err != nil {
return err
}
def, err := parseMethod(ctx, c, fun, lines)
def, err := parseMethod(ctx, c, fn, lines)
if err != nil {
return err
}
Expand All @@ -80,6 +81,7 @@ func parseMethod(ctx *context, c *Converter, obj types.Object, rawMethod RawLine
Fields: map[string]*FieldMapping{},
Location: rawMethod.Location,
EnumMapping: &EnumMapping{Map: map[string]string{}},
localOpts: method.LocalOpts{Context: map[string]bool{}},
}

for _, value := range rawMethod.Lines {
Expand All @@ -97,7 +99,7 @@ func parseMethod(ctx *context, c *Converter, obj types.Object, rawMethod RawLine
ContextMatch: m.ArgContextRegex,
Generated: true,
UpdateParam: m.updateParam,
})
}, m.localOpts)

m.Definition = def

Expand Down Expand Up @@ -137,6 +139,10 @@ func parseMethodLine(ctx *context, c *Converter, m *Method, value string) (err e
}
case "update":
m.updateParam, err = parse.String(rest)
case "context":
var key string
key, err = parse.String(rest)
m.localOpts.Context[key] = true
case "enum:map":
fields := strings.Fields(rest)
if len(fields) != 2 {
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export default defineConfig({
collapsed: true,
items: [
{ text: "autoMap", link: "/reference/autoMap" },
{ text: "context", link: "/reference/context" },
{ text: "default", link: "/reference/default" },
{ text: "ignore", link: "/reference/ignore" },
{ text: "map", link: "/reference/map" },
Expand Down
7 changes: 6 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import GH from './GH.vue';

## unreleased

- Fix not setting `nil` on map when value is `nil`. <GH issue="173" pr="175"/>
- Add [`default:update`](/reference/default.md#default-update-yes-no) <GH issue="171" pr="175"/>
- Add [`context`](./reference/context.md). See [Guide: Pass context to
functions](./guide/context.md) <GH issue="68" pr="176"/>
- Remove default value of [`arg:context:regex`](./reference/arg.md) <GH issue="68" pr="176"/>
- To get v1.6.0 behavior, configure: `goverter:arg:context:regex ^ctx|^context`
- The recommended way to configure context is to use [`context`](./reference/context.md).
- Fix not setting `nil` on map when value is `nil`. <GH issue="173" pr="175"/>

## v1.6.0

Expand Down
4 changes: 1 addition & 3 deletions docs/guide/context.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# How to pass context to custom functions

You can pass additional parameters to custom functions by defining them as
[context](../reference/signature.md#categories). This can be done by prefixing
the parameter names with `context` or `ctx`. Use this guide to get a broad
overview on how to use this feature.
[`context`](../reference/context.md).

If we want to format a `time.Time` to `string` but have requirements so that
the date format must be changeable at runtime. You can define the time format
Expand Down
5 changes: 4 additions & 1 deletion docs/reference/arg.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
argument](./define-settings.md#cli), [conversion
comment](./define-settings.md#conversion) or [method
comment](./define-settings.md#method). This setting is
[inheritable](./define-settings.md#inheritance). Default `^ctx|^context`.
[inheritable](./define-settings.md#inheritance). Default _unset_.

`arg:context:regex` allows you define a regex that automatically defines
arguments as [`context`](./context.md) if the name matches.

::: code-group
<<< @../../example/context/regex/input.go
Expand Down
25 changes: 25 additions & 0 deletions docs/reference/context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Setting: context

## context ARG

`context ARG` is a can be defined as
[custom function comment](./define-settings.md#custom-function).

`context` defines the `ARG` as context. "Context" are additional arguments that
may be used in other custom functions like [`default`](./default.md),
[`extend`](./extend.md), or
[`map CUSTOM`](./map.md#map-source-path-target-method).

<!-- prettier-ignore -->
::: details Example (click me)
::: code-group
<<< @../../example/context/database/input.go
<<< @../../example/context/database/generated/generated.go [generated/generated.go]
:::

<!-- prettier-ignore -->
::: details Example 2 (click me)
::: code-group
<<< @../../example/context/date-format/input.go
<<< @../../example/context/date-format/generated/generated.go [generated/generated.go]
:::
11 changes: 11 additions & 0 deletions docs/reference/define-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ var (
)
```

## Custom Function

You can define settings for custom functions by prefixing them with `goverter:`.

```go
// goverter:setting abc
func FormatInt(value int, abc context.Context) {
return strconv.Itoa(value)
}
```

### Inheritance

Method settings can be inherited for all methods if they are defined on the CLI
Expand Down
7 changes: 7 additions & 0 deletions docs/reference/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ These settings can only be defined as [CLI argument](./define-settings.md#cli) o
These settings can only be defined as [method comment](./define-settings.md#method).

- [`autoMap PATH` automatically match fields from a sub struct to the target struct](./autoMap.md)
- [`context ARG` define an argument as context](./context.md)
- [`default [PACKAGE:]FUNC` define default target value](./default.md)
- [`enum:map SOURCE TARGET` define an enum value mapping](./enum.md#enum-map-source-target)
- [`enum:transform ID CONFIG` use an enum value transformer](./enum.md#enum-transform-id-config)
Expand Down Expand Up @@ -57,3 +58,9 @@ These settings can be defined as [CLI argument](./define-settings.md#cli),
- [`useZeroValueOnPointerInconsistency [yes|no]` Use zero values for `*S` to `T` conversions](./useZeroValueOnPointerInconsistency.md)
- [`wrapErrorsUsing [PACKAGE]` wrap errors using a custom implementation](./wrapErrorsUsing.md)
- [`wrapErrors [yes,no]` wrap errors with extra information](./wrapErrors.md)

## Custom Function

These settings can only be defined as [custom function comment](./define-settings.md#custom-function).

- [`context ARG` define an argument as context](./context.md)
43 changes: 27 additions & 16 deletions docs/reference/signature.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,16 @@ Goverter groups **params** and **results** into these categories:

1. `source` is a **param** that will be converted to the `target` type. Any
**param** that is not a `context` type is a `source` type.
1. `target` is the **first result** which `sources` are converted to.
1. `target` is the **first result** or the argument defined with
[`update`](./update.md) which `sources` are converted to.
1. `target_error` is the **second result** type which can be specified if the
conversion fails. `target_error` must be of type
[`error`](https://go.dev/tour/methods/19)
1. `context` are **[named](#named-paramsresults) params** where the name starts
with `ctx` or `context`. The regex can be adjusted via
[`arg:context:regex`](./arg.md#arg-context-regex). They are used in
[custom functions](#custom-function) for manual conversion. `context` types
aren't used for automatic conversion.
1. `context` are **[named](#named-paramsresults) params** where the argument is
defined with [`context`](./context.md). Or matching the regex
[`arg:context:regex`](./arg.md#arg-context-regex) They are used in [custom
functions](#custom-function) for manual conversion. `context` types aren't
used for automatic conversion.

### Default context

Expand Down Expand Up @@ -87,10 +88,13 @@ type Converter interface {
ConvertTwo(source A) (B, error)
// A=source; B=target; error=target_error

ConvertThree(source A, ctx B) C
// goverter:context b
ConvertThree(source A, b B) C
// A=source; B=context; B=target

ConvertFour(ctxOne A, source B, ctxSecond C) (D, error)
// goverter:context one
// goverter:context two
ConvertFour(one A, source B, two C) (D, error)
// A=context; B=source; C=context; D=target; error=target_error
}
```
Expand All @@ -115,6 +119,7 @@ type Converter interface {
// A=source; B=target
ConvertTwo(source A, target B) error
// A=source; B=target; error=target_error
// goverter:context ctx
ConvertThree(target A, source B, ctx C)
// A=target; B=source; C=context
}
Expand Down Expand Up @@ -146,14 +151,15 @@ func ConvertThree(input A) B {/**/}
func ConvertFour(input A) (B, error) {/**/}
// A=source; B=target; error=target_error

func ConvertFour(ctxOne A, ctxTwo B) C {/**/}
// goverter:context one
// goverter:context two
func ConvertFour(one A, two B) C {/**/}
// A=context; B=context; C=target

func ConvertFive(input A, ctxOne B, ctxTwo C) D {/**/}
// goverter:context one
// goverter:context two
func ConvertFive(input A, one B, two C) D {/**/}
// A=source; B=context; C=context; D=target

func ConvertSix(ctxOne A, source B ctxTwo C) (D, error) {/**/}
// A=context; B=source; C=context; D=target; error=target_error
```

::: details Example (click to expand)
Expand All @@ -180,13 +186,18 @@ func ConvertOne(input A) B {/**/}
func ConvertTwo(input A) (B, error) {/**/}
// A=source; B=target; error=target_error

func ConvertThree(input A, ctxOne B) C {/**/}
// goverter:context one
func ConvertThree(input A, one B) C {/**/}
// A=source; B=context; C=target

func ConvertFour(input A, ctxOne B, ctxTwo C) D {/**/}
// goverter:context one
// goverter:context two
func ConvertFour(input A, one B, two C) D {/**/}
// A=source; B=context; C=context; D=target

func ConvertFive(ctxOne A, source B ctxTwo C) (D, error) {/**/}
// goverter:context one
// goverter:context two
func ConvertFive(one A, source B, two C) (D, error) {/**/}
// A=context; B=source; C=context; D=target; error=target_error
```

Expand Down
8 changes: 5 additions & 3 deletions example/context/database/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package example
// goverter:converter
type Converter interface {
// goverter:map ID Editable | QueryEditable
Convert(source PostInput, ctxDatabase Database) (PostOutput, error)
// goverter:context db
Convert(source PostInput, db Database) (PostOutput, error)
}

func QueryEditable(id int, ctxDatabase Database) bool {
return ctxDatabase.AllowedToEdit(id)
// goverter:context db
func QueryEditable(id int, db Database) bool {
return db.AllowedToEdit(id)
}

type Database interface {
Expand Down
8 changes: 5 additions & 3 deletions example/context/date-format/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import "time"
// goverter:converter
// goverter:extend FormatTime
type Converter interface {
Convert(source map[string]Input, ctxFormat string) map[string]Output
// goverter:context dateFormat
Convert(source map[string]Input, dateFormat string) map[string]Output
}

func FormatTime(t time.Time, ctxFormat string) string {
return t.Format(ctxFormat)
// goverter:context dateFormat
func FormatTime(t time.Time, dateFormat string) string {
return t.Format(dateFormat)
}

type Input struct {
Expand Down
10 changes: 8 additions & 2 deletions method/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,14 @@ type ParseOpts struct {
UpdateParam string
}

type LocalOpts struct {
Context map[string]bool
}

var EmptyLocalOpts = LocalOpts{Context: map[string]bool{}}

// Parse parses an function into a Definition.
func Parse(obj types.Object, opts *ParseOpts) (*Definition, error) {
func Parse(obj types.Object, opts *ParseOpts, localOpts LocalOpts) (*Definition, error) {
methodDef := &Definition{
ID: obj.String(),
OriginID: obj.String(),
Expand Down Expand Up @@ -94,7 +100,7 @@ func Parse(obj types.Object, opts *ParseOpts) (*Definition, error) {
default:
return nil, formatErr("The signature one non 'error' result or multiple results is not supported for goverter:update signatures.")
}
case opts.ContextMatch.MatchString(arg.Name):
case (opts.ContextMatch != nil && opts.ContextMatch.MatchString(arg.Name)) || localOpts.Context[arg.Name]:
methodDef.Context[arg.Type.String] = arg.Type
arg.Use = ArgUseContext
case methodDef.Source == nil:
Expand Down
Loading

0 comments on commit a59de09

Please sign in to comment.