Skip to content

Commit

Permalink
start creating go test server for integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
joshmossas committed Sep 20, 2024
1 parent 8531fb3 commit d025a12
Show file tree
Hide file tree
Showing 39 changed files with 438 additions and 58 deletions.
3 changes: 2 additions & 1 deletion go.work
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
go 1.22.5

use ./languages/go/go-server
use ./playground/go
use ./playground/go
use ./tests/servers/go
29 changes: 24 additions & 5 deletions languages/go/go-server/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

type App[TContext any] struct {
Mux *http.ServeMux
Port uint32
CreateContext func(r *http.Request) (*TContext, RpcError)
InitializationErrors []error
Options AppOptions[TContext]
Expand Down Expand Up @@ -84,18 +83,35 @@ func appDefToFile(appDef AppDef, output string, keyCasing KeyCasing) error {
return nil
}

func printServerStartMessages[TContext any](app *App[TContext], port uint32, isHttps bool) {
protocol := "http"
if isHttps {
protocol = "https"
}
baseUrl := fmt.Sprintf("%v://localhost:%v", protocol, port)
fmt.Printf("Starting server at %v\n", baseUrl)
if len(app.Options.RpcRoutePrefix) > 0 {
fmt.Printf("Procedures path: %v%v\n", baseUrl, app.Options.RpcRoutePrefix)
}
defPath := app.Options.RpcDefinitionPath
if len(defPath) == 0 {
defPath = "/__definition"
}
fmt.Printf("App Definition Path: %v%v\n\n", baseUrl, app.Options.RpcRoutePrefix+defPath)
}

func startServer[TContext any](app *App[TContext], options RunOptions) error {
port := app.Port
port := options.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)
printServerStartMessages(app, port, true)
return http.ListenAndServeTLS(fmt.Sprintf(":%v", port), certFile, keyFile, app.Mux)
}
fmt.Printf("Starting server at http://localhost:%v\n", port)
printServerStartMessages(app, port, false)
return http.ListenAndServe(fmt.Sprintf(":%v", port), app.Mux)
}

Expand Down Expand Up @@ -259,8 +275,11 @@ func RegisterDef[TContext any](app *App[TContext], input any) {
if err != nil {
panic(err)
}
if def.Metadata.IsNone() || def.Metadata.Unwrap().Id.IsNone() {
panic("cannot register anonymous structs as a definition")
}
*app.Definitions = __updateAOrderedMap__(*app.Definitions, __orderedMapEntry__[TypeDef]{
Key: def.Metadata.Unwrap().Id,
Key: def.Metadata.Unwrap().Id.Unwrap(),
Value: *def,
})
}
9 changes: 8 additions & 1 deletion languages/go/go-server/app_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,19 @@ func ToRpcDef(value interface{}, options ArriHttpRpcOptions) (*RpcDef, error) {
if valueKind != reflect.Func {
return nil, errors.ErrUnsupported
}
params := Some(valueType.In(0).Name())
rawParams := valueType.In(0)
params := Some(rawParams.Name())
if rawParams.Name() == "EmptyMessage" && rawParams.PkgPath() == "arrirpc.com/arri" {
params = None[string]()
}
rawResponse := valueType.Out(0)
if rawResponse.Kind() == reflect.Pointer {
rawResponse = rawResponse.Elem()
}
response := Some(rawResponse.Name())
if rawResponse.Name() == "EmptyMessage" && rawResponse.PkgPath() == "arrirpc.com/arri" {
response = None[string]()
}
path := "/" + strcase.ToKebab(fnName)
if len(options.Path) > 0 {
path = options.Path
Expand Down
21 changes: 14 additions & 7 deletions languages/go/go-server/procedures.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,38 @@ func rpc[TParams, TResponse, TContext any](app *App[TContext], serviceName strin
rpcSchema.Http.IsDeprecated = Some(options.IsDeprecated)
}
params := handlerType.In(0)
if params.Kind() != reflect.Struct {
panic("rpc params must be a struct. pointers and other types are not allowed.")
}
hasParams := params.Name() != "EmptyMessage"
if hasParams {
paramsDefContext := _NewTypeDefContext(app.Options.KeyCasing)
paramsSchema, paramsSchemaErr := typeToTypeDef(params, paramsDefContext)
if paramsSchemaErr != nil {
panic(paramsSchemaErr)
}
rpcSchema.Http.Params = Some(paramsSchema.Metadata.Unwrap().Id)
*app.Definitions = __updateAOrderedMap__(*app.Definitions, __orderedMapEntry__[TypeDef]{Key: paramsSchema.Metadata.Unwrap().Id, Value: *paramsSchema})

if paramsSchema.Metadata.IsNone() {
panic("Procedures cannot accept anonymous structs")
}
rpcSchema.Http.Params = paramsSchema.Metadata.Unwrap().Id
*app.Definitions = __updateAOrderedMap__(*app.Definitions, __orderedMapEntry__[TypeDef]{Key: paramsSchema.Metadata.Unwrap().Id.Unwrap(), Value: *paramsSchema})
}
response := handlerType.Out(0)
if response.Kind() == reflect.Ptr {
response = response.Elem()
}
hasResponse := response.Name() != "EmptyMessage"
hasResponse := !(response.Name() == "EmptyMessage" && response.PkgPath() == "arrirpc.com/arri")
if hasResponse {
responseDefContext := _NewTypeDefContext(app.Options.KeyCasing)
responseSchema, responseSchemaErr := typeToTypeDef(response, responseDefContext)
if responseSchemaErr != nil {
panic(responseSchemaErr)
}
rpcSchema.Http.Response = Some(responseSchema.Metadata.Unwrap().Id)
*app.Definitions = __updateAOrderedMap__(*app.Definitions, __orderedMapEntry__[TypeDef]{Key: responseSchema.Metadata.Unwrap().Id, Value: *responseSchema})
if responseSchema.Metadata.IsNone() {
panic("Procedures cannot return anonymous structs")
}
rpcSchema.Http.Response = responseSchema.Metadata.Unwrap().Id
*app.Definitions = __updateAOrderedMap__(*app.Definitions, __orderedMapEntry__[TypeDef]{Key: responseSchema.Metadata.Unwrap().Id.Unwrap(), Value: *responseSchema})
}
rpcName := rpcNameFromFunctionName(GetFunctionName(handler))
if len(serviceName) > 0 {
Expand Down Expand Up @@ -93,7 +101,6 @@ func rpc[TParams, TResponse, TContext any](app *App[TContext], serviceName strin
if onError == nil {
onError = func(r *http.Request, t *TContext, err error) {}
}

paramsZero := reflect.Zero(reflect.TypeFor[TParams]())
app.Mux.HandleFunc(rpcSchema.Http.Path, func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
Expand Down
50 changes: 36 additions & 14 deletions languages/go/go-server/type_def.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const (
type Type = string

type TypeDefMetadata struct {
Id string `key:"id"`
Id Option[string] `key:"id"`
Description Option[string] `key:"description"`
IsDeprecated Option[bool] `key:"isDeprecated"`
}
Expand Down Expand Up @@ -160,6 +160,7 @@ func typeToTypeDef(input reflect.Type, context _TypeDefContext) (*TypeDef, error
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64:
Expand Down Expand Up @@ -231,7 +232,7 @@ func primitiveTypeToTypeDef(value reflect.Type, context _TypeDefContext) (*TypeD
if context.EnumValues.IsSome() {
metadata := None[TypeDefMetadata]()
if context.EnumName.IsSome() {
metadata = Some(TypeDefMetadata{Id: context.EnumName.Unwrap()})
metadata = Some(TypeDefMetadata{Id: Some(context.EnumName.Unwrap())})
}
return &TypeDef{
Enum: context.EnumValues,
Expand Down Expand Up @@ -259,30 +260,47 @@ func IsDiscriminatorStruct(input reflect.Type) bool {
return false
}

func nameFromInstancePath(instancePath string) string {
fmt.Println("INSTANCe_PATH", instancePath)
parts := strings.Split(instancePath, "/")

return strcase.ToCamel(strings.Join(parts, "_"))
}

func structToTypeDef(input reflect.Type, context _TypeDefContext) (*TypeDef, error) {
structName := input.Name()
for i := 0; i < len(context.ParentStructs); i++ {
name := context.ParentStructs[i]
if name == structName {
return &TypeDef{Ref: Some(structName)}, nil
typeId := Some(structName)
isAnonymous := len(input.PkgPath()) == 0 || strings.ContainsAny(structName, " []")
if isAnonymous {
typeId = None[string]()
}
if len(context.ParentStructs) == 0 && isAnonymous {
return nil, fmt.Errorf("root level type definitions cannot be anonymous structs")
}
if typeId.IsSome() {
for i := 0; i < len(context.ParentStructs); i++ {
name := context.ParentStructs[i]
if name == typeId.Unwrap() {
return &TypeDef{Ref: typeId}, nil
}
}
context.ParentStructs = append(context.ParentStructs, typeId.Unwrap())

}
context.ParentStructs = append(context.ParentStructs, structName)
kind := input.Kind()
if kind != reflect.Struct {
return nil, errors.ErrUnsupported
}
if input.NumField() == 0 && input.Name() != "DiscriminatorKey" {
return nil, errors.New("cannot create schema for an empty struct")
}

requiredFields := []__orderedMapEntry__[TypeDef]{}
optionalFields := []__orderedMapEntry__[TypeDef]{}
for i := 0; i < input.NumField(); i++ {
field := input.Field(i)
isDiscriminator := len(field.Tag.Get("discriminator")) > 0 || field.Type.Name() == "DiscriminatorKey"
if isDiscriminator {
return taggedUnionToTypeDef(structName, input, context)
return taggedUnionToTypeDef(typeId, input, context)
}
key := field.Tag.Get("key")
if len(key) == 0 {
Expand Down Expand Up @@ -336,8 +354,12 @@ func structToTypeDef(input reflect.Type, context _TypeDefContext) (*TypeDef, err
if isOptional2 {
fieldType = extractOptionalType(fieldType)
}

instancePath := "/" + structName + "/" + key
var instancePath string
if isAnonymous {
instancePath = context.InstancePath + "/" + key
} else {
instancePath = "/" + typeId.Unwrap() + "/" + key
}
schemaPath := context.SchemaPath + "/properties/" + key
newDepth := context.CurrentDepth + 1
fieldResult, fieldError := typeToTypeDef(
Expand Down Expand Up @@ -387,12 +409,12 @@ func structToTypeDef(input reflect.Type, context _TypeDefContext) (*TypeDef, err
Properties: Some(requiredFields),
OptionalProperties: Some(optionalFields),
Nullable: context.IsNullable,
Metadata: Some(TypeDefMetadata{Id: structName})}, nil
Metadata: Some(TypeDefMetadata{Id: typeId})}, nil
}
return &TypeDef{
Properties: Some(requiredFields),
Nullable: context.IsNullable,
Metadata: Some(TypeDefMetadata{Id: structName}),
Metadata: Some(TypeDefMetadata{Id: typeId}),
}, nil
}

Expand All @@ -410,7 +432,7 @@ func extractNullableType(input reflect.Type) reflect.Type {
return field.Type
}

func taggedUnionToTypeDef(name string, input reflect.Type, context _TypeDefContext) (*TypeDef, error) {
func taggedUnionToTypeDef(name Option[string], input reflect.Type, context _TypeDefContext) (*TypeDef, error) {
kind := input.Kind()
if kind != reflect.Struct {
return nil, errors.ErrUnsupported
Expand Down
20 changes: 13 additions & 7 deletions pnpm-lock.yaml

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

9 changes: 9 additions & 0 deletions tests/servers/go/arri.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { servers } from "arri";

import config from "../ts/arri.config";

config.server = servers.goServer({
cwd: __dirname,
});

export default config;
3 changes: 3 additions & 0 deletions tests/servers/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module arrirpc.com/go-test-server

go 1.22.5
Loading

0 comments on commit d025a12

Please sign in to comment.