ogent

package module
v0.0.0-...-ed3e5d4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 21, 2023 License: Apache-2.0 Imports: 17 Imported by: 0

README

ogent

This package is an extension to the awesome Ent ORM code generator. It utilizes the power of ogen to provide a type-safe, reflection free implementation of the OpenAPI Specification document generated by another popular Ent extension: entoas . ogent generated the missing handler implementations needed by ogen to serve the described API. The data access is done with Ent. To learn more about Ent, how to connect to different types of databases, run migrations or work the Graph model head over to their documentation.

ogen is an opinionated Go code generator for OpenAPI Specification v3 documents. ogen generates both server and client implementations for a given OpenAPI Specification document. The only thing left to do for the user is to implement an interface to access the data layer of any application. ogen has many cool features, one of which is integration with OpenTelemetry. ogent serves as a bridge between Ent and the code generated by ogen. It uses the configuration by the popular Ent extension entoas(https://github.com/ent/contrib/entoas) to generate the missing parts of the ogent code.

Getting Started

The first step is to add the ogent package to your Ent project.

go get ariga.io/ogent@main

ogent uses the Ent Extension API to integrate with Ent’s code-generation. This requires that you use the entc (ent codegen) package as described here. Follow the next three steps to enable it and to configure Ent to work with the ogent extension:

  1. Create a new Go file name ent/entc.go and paste the following content:
//go:build ignore

package main

import (
	"log"

	"ariga.io/ogent"
	"entgo.io/contrib/entoas"
	"entgo.io/ent/entc"
	"entgo.io/ent/entc/gen"
	"github.com/ogen-go/ogen"
)

func main() {
	spec := new(ogen.Spec)
	oas, err := entoas.NewExtension(entoas.Spec(spec))
	if err != nil {
		log.Fatalf("creating entoas extension: %v", err)
	}
	ogent, err := ogent.NewExtension(spec)
	if err != nil {
		log.Fatalf("creating ogent extension: %v", err)
	}
	err = entc.Generate("./schema", &gen.Config{}, entc.Extensions(ogent, oas))
	if err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}

Note, that the order in which you register the extensions do matter. If you have more extensions enabled, make sure that the entoas extension is executed before ogent.

  1. Edit the ent/generate.go file to execute the ent/entc.go instead.
package ent

//go:generate go run -mod=mod entc.go
  1. Run the code generator.
go generate ./...

You should see a bunch of files generated by the Ent code generator. If you are new to Ent and want to learn more about them, have a look at their docs.

If you want to follow along with the next part you can have a look at the example project within this repository.

The files we are interested in reside in the ent/ogent directory. All the files ending in _gen.go are generated by ogen. The file named oas_server_gen.go contains the interface generated by ogen that a user needs to implement in order to use ogen. ogent adds an implementation for that handler in the file ogent.go. To see how you can define what routes to generate and what edges to eager load please head over to the entoas documentation.

ogent generates two more files in that directory: ogent.go containing the implementation code for the ogen interface and responses.go containing helpers to map Ent structs to ogen structs.

Adding custom logic

In order to add HTTP endpoints to your application and document them as well in the OpenAPI Specification document you can extend the spec entoas generates. Additionally ogen provides an easy-to-use API to add or edit parts of the spec. Assume we want to add another route to the document to add a DB health check reachable under the /db-health endpoint. Edit the ent/entc.go to include the endpoint in the OpenAPI Specification:

//go:build ignore

package main

import (
	"log"

	"entgo.io/contrib/entoas"
	"entgo.io/ent/entc"
	"entgo.io/ent/entc/gen"
	"ariga.io/ogent"
	"github.com/ogen-go/ogen"
)

func main() {
	spec := new(ogen.Spec)
	oas, err := entoas.NewExtension(
		entoas.Spec(spec),
		entoas.Mutations(func(graph *gen.Graph, spec *ogen.Spec) error {
			spec.AddPathItem("/db-health", ogen.NewPathItem().
				SetDescription("Check the servers DB status").
				SetGet(ogen.NewOperation().
					SetOperationID("DBHealth").
					SetSummary("Ping the database and report").
					AddResponse("204", ogen.NewResponse().SetDescription("DB is reachable")).
					AddResponse("503", ogen.NewResponse().SetDescription("DB is not reachable")),
				),
			)
			return nil
		}),
	)
	if err != nil {
		log.Fatalf("creating entoas extension: %v", err)
	}
	ogent, err := ogent.NewExtension(spec)
	if err != nil {
		log.Fatalf("creating ogent extension: %v", err)
	}
	err = entc.Generate("./schema", &gen.Config{}, entc.Extensions(ogent, oas))
	if err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}

ogen will add another method to the interface and all you have to do is add an implementation for that. The following main.go would do that:

package main

import (
	"context"
	"database/sql"
	"flag"
	"log"
	"net/http"

	"entgo.io/ent/dialect"
	entsql "entgo.io/ent/dialect/sql"
	"entgo.io/ent/dialect/sql/schema"
	_ "github.com/mattn/go-sqlite3"

	"<your-project>/ent"
	"<your-project>/ent/ogent"
)

type handler struct {
	*ogent.OgentHandler
	db *sql.DB
}

func (h handler) DBHealth(_ context.Context) (ogent.DBHealthRes, error) {
	if err := h.db.Ping(); err != nil {
		return &ogent.DBHealthServiceUnavailable{}, nil
	}
	return &ogent.DBHealthNoContent{}, nil
}

func main() {
	var args struct {
		Addr string
		DSN  string
	}
	flag.StringVar(&args.Addr, "addr", ":8080", "http address to listen")
	flag.StringVar(&args.DSN, "dsn", "file:ent?mode=memory&cache=shared&_fk=1", "dsn of database")
	flag.Parse()
	// Create ent client.
	drv, err := entsql.Open(dialect.SQLite, args.DSN)
	if err != nil {
		log.Fatal(err)
	}
	client := ent.NewClient(ent.Driver(drv))
	// Run the migrations.
	if err := client.Schema.Create(context.Background(), schema.WithAtlas(true)); err != nil {
		log.Fatal(err)
	}
	// Start listening.
	srv := ogent.NewServer(handler{
		OgentHandler: ogent.NewOgentHandler(client),
		db:           drv.DB(),
	})
	if err := http.ListenAndServe(args.Addr, srv); err != nil {
		log.Fatal(err)
	}
}

Customizing Templates

Since ogent is written as an extension to Ent, you can pass in custom templates to customize the generated code by using the Templates() option.

//go:build ignore

package main

import (
	"log"

	"ariga.io/ogent"
	"entgo.io/contrib/entoas"
	"entgo.io/ent/entc"
	"entgo.io/ent/entc/gen"
	"github.com/ogen-go/ogen"
)

var noPagination = gen.MustParse(gen.NewTemplate("").Parse(`
{{ define "ogent/ogent/helper/list/paginate" }}
// Skip pagination
{{ end }}
`))

func main() {
	spec := new(ogen.Spec)
	oas, err := entoas.NewExtension(entoas.Spec(spec))
	if err != nil {
		log.Fatalf("creating entoas extension: %v", err)
	}
	ogent, err := ogent.NewExtension(spec, ogent.Templates(noPagination))
	if err != nil {
		log.Fatalf("creating ogent extension: %v", err)
	}
	err = entc.Generate("./schema", &gen.Config{}, entc.Extensions(ogent, oas))
	if err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}

Documentation

Index

Constants

View Source
const (
	Integer = "integer"
	Number  = "number"
	String  = "string"
	Boolean = "boolean"
)

OAS Schema types.

View Source
const (
	None     = ""
	UUID     = "uuid"
	Date     = "date"
	Time     = "time"
	DateTime = "date-time"
	Duration = "duration"
	URI      = "uri"
	IPv4     = "ipv4"
	IPv6     = "ipv6"
	Byte     = "byte"
	Password = "password"
	Int64    = "int64"
	Int32    = "int32"
	Float    = "float"
	Double   = "double"
)

OAS Schema formats.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Target holds the filepath to write the ogen assets to.
	Target string
	// The Views created by entoas.
	Views map[string]*entoas.View
}

Config is injected into the code generation templates.

func (Config) Name

func (Config) Name() string

Name implements the entc.Annotation interface.

type Edge

type Edge entoas.Edge

type Edges

type Edges []*Edge

type Extension

type Extension struct {
	entc.DefaultExtension
	// contains filtered or unexported fields
}

Extension implements entc.Extension interface providing integration with ogen.

func NewExtension

func NewExtension(spec *ogen.Spec, opts ...ExtensionOption) (*Extension, error)

NewExtension returns a new ogent extension with default configuration.

func (Extension) Annotations

func (ex Extension) Annotations() []entc.Annotation

Annotations of the extension.

func (Extension) Hooks

func (ex Extension) Hooks() []gen.Hook

Hooks of the extension.

func (Extension) Templates

func (ex Extension) Templates() []*gen.Template

Templates of the extension.

type ExtensionOption

type ExtensionOption func(*Extension) error

ExtensionOption allows managing Extension configuration using functional arguments

func Target

func Target(t string) ExtensionOption

Target sets the directory the ogen assets are written to.

func Templates

func Templates(ts ...*gen.Template) ExtensionOption

Templates adds the given templates to the code generator.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL