Skip to content

Commit

Permalink
get basic go server working
Browse files Browse the repository at this point in the history
  • Loading branch information
joshmossas committed Sep 5, 2024
1 parent 1171b3d commit 1055a5e
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 14 deletions.
79 changes: 79 additions & 0 deletions languages/go/go-server/app.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package arri

import (
"flag"
"fmt"
"io"
"net/http"
"os"
"reflect"
"strings"
)

type App[TContext any] struct {
Mux *http.ServeMux
Port uint32
CreateContext func(r *http.Request) (*TContext, *ErrorResponse)
InitializationErrors []error
Options AppOptions[TContext]
Expand All @@ -29,6 +33,62 @@ func (app *App[TContext]) GetAppDefinition() AAppDef {
}
}

func (app *App[TContext]) Run(options RunOptions) error {
defOutput := flag.String("def-out", "", "definition-out")
appDefCmd := flag.NewFlagSet("def", flag.ExitOnError)
appDefOutput := appDefCmd.String("output", "__definition.json", "output")
if len(os.Args) >= 2 {
switch os.Args[1] {
case "def", "definition":
appDefCmd.Parse(os.Args[2:])
return appDefToFile(app.GetAppDefinition(), *appDefOutput, app.Options.KeyCasing)
}
}
if len(os.Args) > 1 {
flag.Parse()
}
if len(*defOutput) > 0 {
err := appDefToFile(app.GetAppDefinition(), *defOutput, app.Options.KeyCasing)
if err != nil {
return err
}
}
return startServer(app, options)
}

func appDefToFile(appDef AAppDef, output string, keyCasing KeyCasing) error {
appDefJson, appDefJsonErr := ToJson(appDef, keyCasing)
if appDefJsonErr != nil {
return appDefJsonErr
}
writeFileErr := os.WriteFile(output, appDefJson, 0644)
if writeFileErr != nil {
return writeFileErr
}
return nil
}

func startServer[TContext any](app *App[TContext], options RunOptions) error {
port := app.Port
if port == 0 {
port = 3000
}
keyFile := options.KeyFile
certFile := options.CertFile
if len(keyFile) > 0 && len(certFile) > 0 {
fmt.Printf("Starting server at https://localhost:%v\n", port)
return http.ListenAndServeTLS(fmt.Sprintf(":%v", port), certFile, keyFile, app.Mux)
}
fmt.Printf("Starting server at http://localhost:%v\n", port)
return http.ListenAndServe(fmt.Sprintf(":%v", port), app.Mux)
}

type RunOptions struct {
Port uint32
CertFile string
KeyFile string
}

type AppOptions[TContext any] struct {
AppName string
// The current app version. Generated clients will send this in the "client-version" header
Expand Down Expand Up @@ -124,9 +184,28 @@ func NewApp[TContext any](mux *http.ServeMux, options AppOptions[TContext], crea
})

mux.HandleFunc(defPath, func(w http.ResponseWriter, r *http.Request) {
ctx, ctxErr := app.CreateContext(r)
if ctxErr != nil {
handleError(false, w, r, ctx, ErrorResponse{Code: 500, Message: ctxErr.Error()}, onError)
return
}
onRequestError := onRequest(r, *ctx)
if onRequestError != nil {
handleError(false, w, r, ctx, ErrorResponse{Code: onRequestError.Code, Message: onRequestError.Error()}, onError)
}
jsonResult, _ := ToJson(app.GetAppDefinition(), app.Options.KeyCasing)
beforeResponseErr := onBeforeResponse(r, *ctx, jsonResult)
if beforeResponseErr != nil {
handleError(false, w, r, ctx, ErrorResponse{Code: beforeResponseErr.Code, Message: beforeResponseErr.Error()}, onError)
return
}
w.WriteHeader(200)
w.Write(jsonResult)
afterResponseErr := onAfterResponse(r, *ctx, jsonResult)
if afterResponseErr != nil {
handleError(true, w, r, ctx, ErrorResponse{Code: afterResponseErr.Code, Message: afterResponseErr.Message}, onError)
return
}
})
return app
}
Expand Down
87 changes: 79 additions & 8 deletions playground/go/arri.config.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import { ChildProcess, execSync, spawn } from "node:child_process";
import fs from "node:fs";

import { defineConfig, defineServerConfig } from "arri";
import chokidar from "chokidar";
import { createConsola } from "consola";

const logger = createConsola().withTag("arri");

const goServer = defineServerConfig({
devFn(_, __) {
devFn(_, generators) {
if (!fs.existsSync(".output")) {
fs.mkdirSync(".output");
}
const watcher = chokidar.watch(["**/*.go"], { ignoreInitial: true });
let childProcess: ChildProcess | undefined;
function spawnProcess() {
async function spawnProcess() {
execSync("go build -o .output/server", {
stdio: "inherit",
});
childProcess = spawn(".output/server", {
stdio: "inherit",
});
childProcess = spawn(
".output/server",
["--def-out=.output/__definition.json"],
{
stdio: "inherit",
},
);
}

async function closeChildProcess() {
if (!childProcess) return true;
return new Promise((res, rej) => {
Expand All @@ -28,7 +40,7 @@ const goServer = defineServerConfig({
}
spawnProcess();
watcher.on("all", async (evt) => {
console.info(`[arri] changed detected restarting server`);
logger.info(`Changed detected. Restarting server...`);
switch (evt) {
case "addDir":
return;
Expand All @@ -38,11 +50,70 @@ const goServer = defineServerConfig({
}
}
});
if (generators.length == 0) {
logger.warn(
"No generators specified in arri config. Skipping codegen.",
);
return;
}
let defFileCache = "";
let hasRunGenerators = false;
async function handleAppDef() {
const defFile = fs.readFileSync(
".output/__definition.json",
"utf8",
);
if (defFile == defFileCache) {
return;
}
const startTime = new Date();
if (hasRunGenerators) {
logger.info("App definition updated. Rerunning generators...");
} else {
logger.info("Running generators...");
}
defFileCache = defFile;
const appDef = JSON.parse(defFile);
await Promise.allSettled(
generators.map((gen) => gen.generator(appDef, true)),
);
logger.success(
`Ran ${generators.length} generator(s) in ${new Date().getTime() - startTime.getTime()}ms`,
);
hasRunGenerators = true;
}
const appDefWatcher = chokidar.watch([".output/__definition.json"]);
appDefWatcher.on("change", () => {
handleAppDef();
});
},
buildFn(_, __) {
execSync(`go build`, {
async buildFn(_, generators) {
if (!fs.existsSync(".output")) {
fs.mkdirSync(".output");
}
execSync(`go build -o .output/server`, {
stdio: "inherit",
});
execSync(`.output/server def -output .output/__definition.json`, {
stdio: "inherit",
});
if (generators.length === 0) {
logger.warn(
"No generators specified in arri config. Skipping codegen.",
);
return;
}
const startTime = new Date();
logger.info(`Running generators...`);
const appDef = JSON.parse(
fs.readFileSync(".output/__definition.json", "utf8"),
);
await Promise.all(
generators.map((gen) => gen.generator(appDef, false)),
);
logger.success(
`Ran ${generators.length} generator(s) in ${new Date().getTime() - startTime.getTime()}ms`,
);
},
});

Expand Down
11 changes: 6 additions & 5 deletions playground/go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"log"
"net/http"
"os"
)

type MyCustomContext struct{}
Expand All @@ -16,7 +15,6 @@ func onRequest(r *http.Request, c MyCustomContext) *arri.ErrorResponse {
}

func main() {
fmt.Println("Starting server")
mux := http.DefaultServeMux
options := arri.AppOptions[MyCustomContext]{
AppName: "My Awesome App",
Expand All @@ -37,7 +35,6 @@ func main() {
return &MyCustomContext{}, nil
}),
)
fmt.Println(os.Getenv("ARRI_DEV"))
// register an RPC
arri.Rpc(&app, GetUser)
arri.Rpc(&app, DeleteUser)
Expand All @@ -47,8 +44,11 @@ func main() {
Method: arri.HttpMethodPatch,
Path: "/update-user",
}, UpdateUser)
log.Println("starting server at http://localhost:4040")
log.Fatal(http.ListenAndServe(":4040", mux))
appErr := app.Run(arri.RunOptions{})
if appErr != nil {
log.Fatal(appErr)
return
}
}

type UserParams struct {
Expand All @@ -59,6 +59,7 @@ type User struct {
Name arri.Option[string]
Email string
IsAdmin bool
Foo string
}

func DeleteUser(params UserParams, context MyCustomContext) (*User, *arri.ErrorResponse) {
Expand Down
3 changes: 2 additions & 1 deletion playground/go/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"devDependencies": {
"@types/node": "22.5.0",
"arri": "workspace:*",
"chokidar": "^3.6.0"
"chokidar": "^3.6.0",
"consola": "^3.2.3"
}
}
1 change: 1 addition & 0 deletions playground/go/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"targets": {
"build": {
"executor": "nx:run-commands",
"cache": false,
"options": {
"command": "arri build",
"cwd": "playground/go"
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1055a5e

Please sign in to comment.