Skip to content

Commit

Permalink
context.go refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
romsar committed Aug 10, 2024
1 parent 1773816 commit 5b15dd9
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 254 deletions.
59 changes: 35 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,34 @@ Inertia allows you to create fully client-side rendered single-page apps without
This package based on the official Laravel adapter for Inertia.js [inertiajs/inertia-laravel](https://github.com/inertiajs/inertia-laravel), supports all the features and works in the most similar way.

## Roadmap

- [x] Tests
- [x] Helpers for testing
- [x] Helpers for validation errors
- [x] Examples
- [x] SSR

## Installation

Install using `go get` command:

```shell
go get github.com/romsar/gonertia
```

## Usage

### Basic example

Initialize Gonertia in your `main.go`:

```go
package main

import (
"log"
"net/http"

inertia "github.com/romsar/gonertia"
)

Expand All @@ -65,7 +70,7 @@ func homeHandler(i *inertia.Inertia) http.Handler {
err := i.Render(w, r, "Home/Index", inertia.Props{
"some": "data",
})

if err != nil {
handleServerErr(w, err)
return
Expand All @@ -77,20 +82,21 @@ func homeHandler(i *inertia.Inertia) http.Handler {
```

Create `root.html` template:

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Put here your styles, meta and other stuff -->
{{ .inertiaHead }}
</head>

<body>
{{ .inertia }}
<script type="module" src="/build/assets/app.js"></script>
</body>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Put here your styles, meta and other stuff -->
{{ .inertiaHead }}
</head>

<body>
{{ .inertia }}
<script type="module" src="/build/assets/app.js"></script>
</body>
</html>
```

Expand Down Expand Up @@ -177,38 +183,41 @@ i.ShareTemplateFunc("trim", strings.TrimSpace)
#### Pass template data via context (in middleware)

```go
ctx := inertia.WithTemplateData(r.Context(), "title", "Home page")
ctx := inertia.SetTemplateData(r.Context(), inertia.TemplateData{"foo", "bar"})
// or inertia.SetTemplateDatum(r.Context(), "foo", "bar")

// pass it to the next middleware or inertia.Render function using r.WithContext(ctx).
```

#### Share prop globally ([learn more](https://inertiajs.com/shared-data))

```go
i.ShareProp("name", "Roman")
i.ShareProp("foo", "bar")
```

#### Pass props via context (in middleware)

```go
ctx := inertia.WithProp(r.Context(), "name", "Roman")
// or inertia.WithProps(r.Context(), inertia.Props{"name": "Roman"})
ctx := inertia.SetProps(r.Context(), inertia.Props{"foo": "bar"})
// or inertia.SetProp(r.Context(), "foo", "bar")

// pass it to the next middleware or inertia.Render function using r.WithContext(ctx).
```

#### Validation errors ([learn more](https://inertiajs.com/validation))

```go
ctx := inertia.WithValidationError(r.Context(), "some_field", "some error")
// or inertia.WithValidationErrors(r.Context(), inertia.ValidationErrors{"some_field": "some error"})
ctx := inertia.SetValidationErrors(r.Context(), inertia.ValidationErrors{"some_field": "some error"})
// or inertia.AddValidationErrors(r.Context(), inertia.ValidationErrors{"some_field": "some error"})
// or inertia.SetValidationError(r.Context(), "some_field", "some error")

// pass it to the next middleware or inertia.Render function using r.WithContext(ctx).
```

#### Replace standard JSON marshaller

1. Implement [JSONMarshaller](./json.go) interface:

```go
import jsoniter "github.com/json-iterator/go"

Expand All @@ -224,6 +233,7 @@ func (j jsonIteratorMarshaller) Marshal(v interface{}) ([]byte, error) {
```

2. Provide your implementation in constructor:

```go
i, err := inertia.New(
/* ... */,
Expand Down Expand Up @@ -255,7 +265,7 @@ i, err := inertia.New(
Unfortunately (or fortunately) we do not have the advantages of such a framework as Laravel in terms of session management.
In this regard, we have to do some things manually that are done automatically in frameworks.

One of them is displaying validation errors after redirects.
One of them is displaying validation errors after redirects.
You have to write your own implementation of `gonertia.FlashProvider` which will have to store error data into the user's session and return this data (you can get the session ID from the context depending on your application).

```go
Expand All @@ -266,6 +276,7 @@ i, err := inertia.New(
```

Simple inmemory implementation of flash provider:

```go
type InmemFlashProvider struct {
errors map[string]inertia.ValidationErrors
Expand Down Expand Up @@ -295,16 +306,16 @@ Of course, this package provides convenient interfaces for testing!

```go
func TestHomepage(t *testing.T) {
body := ... // get an HTML or JSON using httptest package or real HTTP request.
body := ... // get an HTML or JSON using httptest package or real HTTP request.

// ...

assertable := inertia.AssertFromReader(t, body) // from io.Reader body
// OR
assertable := inertia.AssertFromBytes(t, body) // from []byte body
// OR
assertable := inertia.AssertFromString(t, body) // from string body

// now you can do assertions using assertable.Assert[...] methods:
assertable.AssertComponent("Foo/Bar")
assertable.AssertVersion("foo bar")
Expand Down
153 changes: 47 additions & 106 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package gonertia

import (
"context"
"fmt"
)

type contextKey int
Expand All @@ -13,132 +12,74 @@ const (
validationErrorsContextKey
)

// WithTemplateData appends template data value to the passed context.Context.
func WithTemplateData(ctx context.Context, key string, val any) context.Context {
if ctxData := ctx.Value(templateDataContextKey); ctxData != nil {
ctxData, ok := ctxData.(TemplateData)

if ok {
ctxData[key] = val
return context.WithValue(ctx, templateDataContextKey, ctxData)
}
}
// SetTemplateData sets template data to the passed context.Context.
func SetTemplateData(ctx context.Context, templateData TemplateData) context.Context {
return context.WithValue(ctx, templateDataContextKey, templateData)
}

return context.WithValue(ctx, templateDataContextKey, TemplateData{
key: val,
})
// SetTemplateDatum sets single template data item to the passed context.Context.
func SetTemplateDatum(ctx context.Context, key string, val any) context.Context {
templateData := TemplateDataFromContext(ctx)
templateData[key] = val
return SetTemplateData(ctx, templateData)
}

// TemplateDataFromContext returns template data from the context.
func TemplateDataFromContext(ctx context.Context) (TemplateData, error) {
ctxData := ctx.Value(templateDataContextKey)

if ctxData != nil {
data, ok := ctxData.(TemplateData)
if !ok {
return nil, fmt.Errorf("template data in the context has invalid type")
}

return data, nil
func TemplateDataFromContext(ctx context.Context) TemplateData {
templateData, ok := ctx.Value(templateDataContextKey).(TemplateData)
if ok {
return templateData
}

return TemplateData{}, nil
return TemplateData{}
}

// WithProp appends prop value to the passed context.Context.
func WithProp(ctx context.Context, key string, val any) context.Context {
if ctxData := ctx.Value(propsContextKey); ctxData != nil {
ctxData, ok := ctxData.(Props)

if ok {
ctxData[key] = val
return context.WithValue(ctx, propsContextKey, ctxData)
}
}

return context.WithValue(ctx, propsContextKey, Props{
key: val,
})
// SetProps sets props values to the passed context.Context.
func SetProps(ctx context.Context, props Props) context.Context {
return context.WithValue(ctx, propsContextKey, props)
}

// WithProps appends props values to the passed context.Context.
func WithProps(ctx context.Context, props Props) context.Context {
if ctxData := ctx.Value(propsContextKey); ctxData != nil {
ctxData, ok := ctxData.(Props)

if ok {
for key, val := range props {
ctxData[key] = val
}

return context.WithValue(ctx, propsContextKey, ctxData)
}
}

return context.WithValue(ctx, propsContextKey, props)
// SetProp sets prop value to the passed context.Context.
func SetProp(ctx context.Context, key string, val any) context.Context {
props := PropsFromContext(ctx)
props[key] = val
return SetProps(ctx, props)
}

// PropsFromContext returns props from the context.
func PropsFromContext(ctx context.Context) (Props, error) {
ctxData := ctx.Value(propsContextKey)

if ctxData != nil {
props, ok := ctxData.(Props)
if !ok {
return nil, fmt.Errorf("props in the context have invalid type")
}

return props, nil
func PropsFromContext(ctx context.Context) Props {
props, ok := ctx.Value(propsContextKey).(Props)
if ok {
return props
}

return Props{}, nil
return Props{}
}

// WithValidationError appends validation error to the passed context.Context.
func WithValidationError(ctx context.Context, key string, msg any) context.Context {
if ctxData := ctx.Value(validationErrorsContextKey); ctxData != nil {
ctxData, ok := ctxData.(ValidationErrors)

if ok {
ctxData[key] = msg
return context.WithValue(ctx, validationErrorsContextKey, ctxData)
}
}

return context.WithValue(ctx, validationErrorsContextKey, ValidationErrors{
key: msg,
})
// SetValidationErrors sets validation errors to the passed context.Context.
func SetValidationErrors(ctx context.Context, errors ValidationErrors) context.Context {
return context.WithValue(ctx, validationErrorsContextKey, errors)
}

// WithValidationErrors appends validation errors to the passed context.Context.
func WithValidationErrors(ctx context.Context, errors ValidationErrors) context.Context {
if ctxData := ctx.Value(validationErrorsContextKey); ctxData != nil {
ctxData, ok := ctxData.(ValidationErrors)

if ok {
for key, msg := range errors {
ctxData[key] = msg
}

return context.WithValue(ctx, validationErrorsContextKey, ctxData)
}
// AddValidationErrors appends validation errors to the passed context.Context.
func AddValidationErrors(ctx context.Context, errors ValidationErrors) context.Context {
validationErrors := ValidationErrorsFromContext(ctx)
for key, val := range errors {
validationErrors[key] = val
}
return SetValidationErrors(ctx, validationErrors)
}

return context.WithValue(ctx, validationErrorsContextKey, errors)
// SetValidationError sets validation error to the passed context.Context.
func SetValidationError(ctx context.Context, key string, msg string) context.Context {
validationErrors := ValidationErrorsFromContext(ctx)
validationErrors[key] = msg
return SetValidationErrors(ctx, validationErrors)
}

// ValidationErrorsFromContext returns validation errors from the context.
func ValidationErrorsFromContext(ctx context.Context) (ValidationErrors, error) {
ctxData := ctx.Value(validationErrorsContextKey)

if ctxData != nil {
validationErrors, ok := ctxData.(ValidationErrors)
if !ok {
return nil, fmt.Errorf("validation errors in the context have invalid type")
}

return validationErrors, nil
func ValidationErrorsFromContext(ctx context.Context) ValidationErrors {
validationErrors, ok := ctx.Value(validationErrorsContextKey).(ValidationErrors)
if ok {
return validationErrors
}

return ValidationErrors{}, nil
return ValidationErrors{}
}
Loading

0 comments on commit 5b15dd9

Please sign in to comment.