alef 0.23.38

Opinionated polyglot binding generator for Rust libraries
Documentation
{#- Go app harness for server-pattern e2e tests
   <!-- go-ffi-nonblock -->

   This harness program is spawned as a subprocess by main_test.go and runs the
   SUT app, registering handlers per fixture. It loads all fixtures, creates
   handlers that return expected responses, and serves on a configured port.

   Context variables (passed from Go codegen):
   - imports: list of import paths to include (typically the package being tested)
   - app_class: class name for SUT app (e.g., "App")
   - route_builder_class: class name for route builder (e.g., "RouteBuilder")
   - method_enum_import: import path for Method (e.g., "my_app")
   - method_enum_class: class name for method enum (e.g., "GET", "POST")
   - register_route_method: app method to register routes (e.g., "RegisterRoute")
   - start_background_method: non-blocking serve entrypoint (e.g., "StartBackground")
   - port: binding port (e.g., 8012)
   - fixtures_json: raw JSON string with all fixtures (auto-serialized)
#}
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/signal"
	"strings"
	"syscall"

{%- for import_name in imports %}
	{{ import_alias }} "{{ import_name }}"
{%- endfor %}
)

// Fixture describes an HTTP test fixture with its handler route and expected response.
type Fixture struct {
	HTTP *HTTPFixture `json:"http"`
}

// HTTPFixture contains handler and expected response details.
type HTTPFixture struct {
	Handler          HandlerDetails `json:"handler"`
	ExpectedResponse ResponseDetails `json:"expected_response"`
}

// HandlerDetails specifies the route and HTTP method for the handler.
type HandlerDetails struct {
	Route      string      `json:"route"`
	Method     string      `json:"method"`
	BodySchema interface{} `json:"body_schema"`
}

// ResponseDetails defines the expected status, body, and headers.
type ResponseDetails struct {
	StatusCode uint16            `json:"status_code"`
	Body       interface{}       `json:"body"`
	Headers    map[string]string `json:"headers"`
}

const port = {{ port }}
const host = "127.0.0.1"

// Load fixtures from the JSON payload embedded at codegen time.
// Use a quoted string with escaped newlines to avoid backtick conflicts.
var fixturesJSON = "{{ fixtures_json }}"

func main() {
	var fixtures map[string]Fixture
	if err := json.Unmarshal([]byte(fixturesJSON), &fixtures); err != nil {
		log.Fatalf("unmarshal fixtures: %v", err)
	}

	app, err := {{ import_alias }}.NewApp()
	if err != nil {
		log.Fatalf("new app: %v", err)
	}

	// Map fixture method names (UPPERCASE) to framework method enum constants (PascalCase).
	methodMap := map[string]{{ import_alias }}.Method{
		"GET":     {{ import_alias }}.MethodGet,
		"POST":    {{ import_alias }}.MethodPost,
		"PUT":     {{ import_alias }}.MethodPut,
		"PATCH":   {{ import_alias }}.MethodPatch,
		"DELETE":  {{ import_alias }}.MethodDelete,
		"HEAD":    {{ import_alias }}.MethodHead,
		"OPTIONS": {{ import_alias }}.MethodOptions,
		"CONNECT": {{ import_alias }}.MethodConnect,
		"TRACE":   {{ import_alias }}.MethodTrace,
	}

	// Register a handler for each fixture.
	for fixtureID, fixture := range fixtures {
		if fixture.HTTP == nil {
			continue
		}

		http := fixture.HTTP
		route := http.Handler.Route
		method := http.Handler.Method
		bodySchema := http.Handler.BodySchema
		expectedStatus := http.ExpectedResponse.StatusCode
		expectedBody := http.ExpectedResponse.Body
		expectedHeaders := http.ExpectedResponse.Headers

		// Build a closure that captures the expected response for this fixture.
		// The handler ignores request parameters and returns the recorded response.
		handler := makeHandler(expectedStatus, expectedBody, expectedHeaders)

		// Register the route with the full fixture-namespaced path: /fixtures/<fixture_id>{route}
		fullRoute := fmt.Sprintf("/fixtures/%s%s", fixtureID, route)

		// Convert method string (GET, POST, etc.) to the framework's method enum.
		m, ok := methodMap[strings.ToUpper(method)]
		if !ok {
			log.Printf("skipping fixture %s: unknown method %s", fixtureID, method)
			continue
		}
		builder, err := {{ import_alias }}.RouteBuilderNew(m, fullRoute)
		if err != nil {
			log.Fatalf("create route builder: %v", err)
		}

		// If there's a body schema, attach it to the builder.
		if bodySchema != nil {
			bodySchemaJSON, _ := json.Marshal(bodySchema)
			builder = builder.RequestSchemaJSON(json.RawMessage(bodySchemaJSON))
		}

		// Register the route with the handler.
		// Note: The builder is consumed by RegisterRoute (ownership transferred to Rust).
		// Do NOT call builder.Free() — the Rust FFI layer already owns and frees it.
		if err := app.{{ register_route_method }}(handler, builder); err != nil {
			log.Fatalf("register route %s: %v", fullRoute, err)
		}
	}

	// Start the server on a background OS thread.  StartBackground blocks until
	// the TCP socket is bound, so the server is guaranteed to be accepting
	// connections when this call returns.  This avoids the goroutine + polling
	// pattern that previously exhausted cgo OS threads.
	serverHandle, err := app.{{ start_background_method }}(host, port)
	if err != nil {
		log.Fatalf("start background server on %s:%d: %v", host, port, err)
	}

	// Signal readiness to the parent test process.
	fmt.Printf("Harness listening on %s:%d\n", host, port)
	os.Stdout.Sync()

	// Graceful shutdown on interrupt.
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
	<-sigChan
	serverHandle.Stop()
}

// makeHandler returns a handler function that always returns the expected response.
func makeHandler(statusCode uint16, body interface{}, headers map[string]string) func([]byte) ([]byte, error) {
	return func(reqBody []byte) ([]byte, error) {
		// Wrap the response in the framework's Response struct.
		resp := map[string]interface{}{
			"status_code": statusCode,
			"content":     body,
			"headers":     headers,
		}
		respBody, _ := json.Marshal(resp)
		return respBody, nil
	}
}