Giter Club home page Giter Club logo

ogent's Introduction

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)
	}
}

ogent's People

Contributors

masseelch avatar dependabot[bot] avatar tdakkota avatar ernado avatar rolandknight avatar agola11 avatar thisisibrahimd avatar michael-doubez avatar idledaemon avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.