Documentation
¶
Overview ¶
Package rpc lets one app call functions of another. The called app declares them in its own rpc/ folder, package rpc, as plain Go functions with an //ssr:access line:
// Contacts returns the phones and email addresses of a user. // //ssr:access caller=people role=staff func Contacts(ctx context.Context, in ContactsIn) (ContactsOut, error)
caller= names the apps that may call the function. role= is the rule for calls made on a viewer's behalf: roles joined by |, or * for every viewer. apps=true lets the listed apps call with no viewer. Inside rpc/, import this package under another name, such as frameworkrpc.
aicoded generate writes the server, rpc/server_gen.go, whose rpc.Server() main passes to app.Main as RPC, and the snapshot .aicoded/rpc.json. In the calling app, aicoded rpc add <app> copies the snapshot and generates a typed client in services/<app>/. Call it with the context of the request being served, so that the call goes on behalf of its viewer. The runner and the called app both check every call against the permission lists of the two apps.
A function tells its caller why it failed with Error or Errorf and one of six codes: InvalidArgument, NotFound, AlreadyExists, PermissionDenied, FailedPrecondition and Unavailable. The caller reads the code with CodeOf. Any other error, and a panic, reach the caller as Internal with the message "internal error", so the called app's details stay in its own logs. A function that returns a viewer's records checks ownership itself, with auth.Viewer(ctx): the calling app's checks are not its own.
Read more in the guide docs/guides/calls-between-apps.md and the task docs/tasks/call-another-app.md, which aicoded explain and the MCP tool howto print as guides/calls-between-apps and tasks/call-another-app.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Call ¶
Call calls method of app with the encoded input in and returns the encoded output. The generated client functions call it; app code calls those instead. The call goes as the viewer of the request ctx belongs to, or as this app when ctx has no viewer. A call whose context has no deadline gets runnerproto.DefaultCallDeadline; the runner cuts any call at runnerproto.MaxCallDeadline.
A failed call returns an error whose message is the callee's or the runner's. Only a refusal the runner marks with runnerproto.OriginHeader reaches the caller as a coded error, whose text starts with the code and carries its fix line; a called app's own refusal reaches the caller as plain text, whatever it looks like. CodeOf reads the status of every error, and errors.Is matches context.Canceled or context.DeadlineExceeded when the call's context ended first.
Generated code only.
func DecodeError ¶
DecodeError marks err as a failure to decode a call's input. Generated code uses it; the caller gets InvalidArgument.
Generated code only.
func Error ¶
Error returns an error that reaches the caller with code and msg. A code other than InvalidArgument, NotFound, AlreadyExists, PermissionDenied, FailedPrecondition or Unavailable reaches the caller as Internal with the message "internal error", like any other error.
Example ¶
A function of an app's rpc/ package tells its caller why it failed with one of six codes. Any other error reaches the caller as Internal, with the message "internal error".
package main
import (
"errors"
"fmt"
"aicoded.dev/framework/rpc"
)
func main() {
err := rpc.Errorf(rpc.NotFound, "no user %q", "zoe")
fmt.Println(rpc.CodeOf(err), err)
fmt.Println(rpc.CodeOf(errors.New("database is locked")))
}
Output: not_found no user "zoe" internal
Types ¶
type Code ¶
type Code int
Code is the status of a failed call.
const ( InvalidArgument Code = iota + 1 NotFound AlreadyExists PermissionDenied FailedPrecondition Internal )
The codes a function may return with Error. Internal is what the caller sees for any other error.
func CodeOf ¶
CodeOf returns the code of err: 0 for nil, the code of an error from Error, Errorf or a call, and Internal for any other error.
Example ¶
The calling page turns the codes it expects into answers for the viewer, and fails on any other error.
package main
import (
"fmt"
"aicoded.dev/framework/rpc"
"aicoded.dev/framework/web"
)
func main() {
answer := func(err error) error {
switch rpc.CodeOf(err) {
case 0:
return nil
case rpc.PermissionDenied:
return web.Forbidden()
case rpc.NotFound:
return web.NotFound()
default:
return err
}
}
fmt.Println(answer(rpc.Error(rpc.PermissionDenied, "not your contacts")))
fmt.Println(answer(rpc.Error(rpc.NotFound, "no such user")))
}
Output: 403 You do not have access to this page. 404 This page does not exist.
type Method ¶
type Method struct {
Name string
// Callers are the apps that may call the function.
Callers []string
// Require holds the rules a viewer must meet, each roles joined by "|", or "*" for any
// viewer. With no rules, no viewer may call the function.
Require []string
// Apps allows calls an app makes as itself, with no viewer.
Apps bool
// Call decodes the input, runs the function and encodes its output.
Call func(ctx context.Context, in []byte) ([]byte, error)
}
Method is one function an app serves, as the generated table describes it.