alef 0.25.5

Opinionated polyglot binding generator for Rust libraries
Documentation
// ServerHandle allows stopping a service started via StartBackground.
type ServerHandle struct {
	service *{{ service_name }}
}

// Stop gracefully shuts down the server.
func (h *ServerHandle) Stop() error {
	if h.service == nil {
		return errors.New("service already stopped")
	}
	h.service.Close()
	h.service = nil
	return nil
}

// StartBackground starts the service in a background goroutine and returns a handle.
// It blocks until the TCP socket is bound, so the server is guaranteed to be accepting
// connections when this call returns.
func (s *{{ service_name }}) StartBackground(host string, port uint16) (*ServerHandle, error) {
	// Configure the service with the provided host and port.
	hostCopy := host
	portCopy := port
	workers := uint(1)
	maxBodySize := uint(10 * 1024 * 1024)
	enableReqID := false
	gracefulShutdown := true
	shutdownTimeout := uint64(30)
	enableHTTPTrace := false
	maxQueue := uint(1024)
	maxConcurrent := uint(128)
	drainTimeout := uint64(30)

	config := &ServerConfig{
		Host:             &hostCopy,
		Port:             &portCopy,
		Workers:          &workers,
		EnableRequestID:  enableReqID,
		MaxBodySize:      &maxBodySize,
		GracefulShutdown: &gracefulShutdown,
		ShutdownTimeout:  &shutdownTimeout,
		EnableHTTPTrace:  enableHTTPTrace,
		BackgroundTasks: BackgroundTaskConfig{
			MaxQueueSize:       &maxQueue,
			MaxConcurrentTasks: &maxConcurrent,
			DrainTimeoutSecs:   &drainTimeout,
		},
	}

	// Ensure static_files is present in JSON even if empty (for Rust compatibility).
	c_configJSON, _ := json.Marshal(config)
	var m map[string]interface{}
	json.Unmarshal(c_configJSON, &m)
	if _, has := m["static_files"]; !has {
		m["static_files"] = []interface{}{}
	}
	c_configJSON, _ = json.Marshal(m)

	c_str := C.CString(string(c_configJSON))
	defer C.free(unsafe.Pointer(c_str))
	c_config := C.{{ ffi_prefix }}server_config_from_json(c_str)
	if c_config == nil {
		errCode := C.{{ ffi_prefix }}last_error_code()
		errMsg := C.GoString(C.{{ ffi_prefix }}last_error_context())
		return nil, fmt.Errorf("configure host/port: ServerConfig config failed: error code %d: %s", errCode, errMsg)
	}

	new_owner := C.{{ ffi_prefix }}app_config((*C.{{ ffi_prefix | upper }}AppOpaque)(s.owner), c_config)
	if new_owner == nil {
		return nil, errors.New("configure host/port: app config failed")
	}
	s.owner = unsafe.Pointer(new_owner)

	// Lock to check ownership and then spawn Run in a goroutine.
	s.mu.Lock()
	if s.owner == nil {
		s.mu.Unlock()
		return nil, errors.New("service is closed")
	}
	s.mu.Unlock()

	// Spawn Run in a goroutine.
	go func() {
		_ = s.Run()
	}()

	// Poll TCP socket until it's bound or timeout (5 seconds).
	deadline := time.Now().Add(5 * time.Second)
	for time.Now().Before(deadline) {
		conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), 100*time.Millisecond)
		if err == nil {
			conn.Close()
			break
		}
		time.Sleep(50 * time.Millisecond)
	}

	// Return a handle for shutdown.
	return &ServerHandle{service: s}, nil
}