mirror of
https://gitlab.com/fabinfra/fabaccess/fabaccess-api.git
synced 2025-03-12 14:51:42 +01:00
174 lines
5.8 KiB
Cap'n Proto
174 lines
5.8 KiB
Cap'n Proto
|
@0x8c2f829df1930cd5;
|
||
|
|
||
|
using Rust = import "programming_language/rust.capnp";
|
||
|
$Rust.parentModule("schema");
|
||
|
|
||
|
using CSharp = import "programming_language/csharp.capnp";
|
||
|
$CSharp.namespace("FabAccessAPI.Schema");
|
||
|
|
||
|
using Persistent = import "/capnp/persistent.capnp".Persistent;
|
||
|
using Value = import "/capnp/schema.capnp".Value;
|
||
|
|
||
|
using General = import "general.capnp";
|
||
|
using User = import "user.capnp".User;
|
||
|
using Space = import "space.capnp".Space;
|
||
|
|
||
|
struct Node {
|
||
|
# A node in the state tree. If it's the root note this struct "contains" the whole tree.
|
||
|
|
||
|
# TODO: I'm not happy with this representation. While it's about as generic as we can get it's
|
||
|
# unhandly because all clients and servers have to always manually check every leaf of the
|
||
|
# state tree, relying on convention instead of static type checking. But I'm not sure how else
|
||
|
# to represent the state extensibly in a way that lets us evolve the protocol by stabilizing
|
||
|
# extensions. One option could be to use OID or UUID as "tag bits" and "stabilize" them by
|
||
|
# defining those as `const` values, but that wouldn't give us proper type checking either.
|
||
|
|
||
|
part @0 :Text;
|
||
|
# Name of the node, making up a path to this node (e.g. "set/colour/red")
|
||
|
|
||
|
union {
|
||
|
# Content of a node. A node has either children *or* a value, not both.
|
||
|
|
||
|
children @1 :List(Node);
|
||
|
# Node is not a leaf node ⇒ it has a list of children
|
||
|
|
||
|
value @2 :Value;
|
||
|
# Node is a leaf node ⇒ it contains a (typed) Cap'n Proto value.
|
||
|
# The type `Value` comes from the Cap'n Proto schema definition file (usually
|
||
|
# /usr/include/capnp/schema.capnp) and can be any basic capnp type, including lists and
|
||
|
# structs (as :AnyPointer which a client has to cast)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct Applied {
|
||
|
# Encodes if a specific actor has applied/verified a state change
|
||
|
|
||
|
name @0 :Text;
|
||
|
# Name of the actor
|
||
|
|
||
|
state @1 :State;
|
||
|
# State of the state change in the actor
|
||
|
enum State {
|
||
|
unapplied @0;
|
||
|
applied @1;
|
||
|
verified @2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
interface Access {
|
||
|
# Allow syncronous read access to a resource's state. You're not given this capability directly
|
||
|
# but instead Notify, Interest and Claim all extend it, allowing you to call these methods from
|
||
|
# any of those.
|
||
|
|
||
|
readState @0 Node;
|
||
|
readApplied @1 Applied;
|
||
|
|
||
|
# TODO: There should probably be a more efficient approach for reading state than "read *all*
|
||
|
# state".
|
||
|
}
|
||
|
|
||
|
interface Notify extends(Access) {
|
||
|
# The Notify interface allows clients to be informed about state changes asyncronously.
|
||
|
# It is mainly designed around the `register` function which allows a client to register a
|
||
|
# Callback on the client that is called every time state changes happen to the resource in
|
||
|
# question.
|
||
|
# Notify are ephermal. If the connection to the server is lost all `Notify` are unregistered
|
||
|
|
||
|
register @0 ( cb: Callback );
|
||
|
# Register a given callback to be called on every state update. If this client already has a
|
||
|
# callback registered for this resource the old callback is replaced.
|
||
|
# The two fields `state` and `applied` indicate interest for `state` and `applied`. If they are
|
||
|
# unset the respective method on the callback will not be called.
|
||
|
|
||
|
unregister @1 ();
|
||
|
# Unregister the current callback, if any.
|
||
|
|
||
|
interface Callback {
|
||
|
# This callback interface is implemented on the client
|
||
|
|
||
|
newState @0 Node;
|
||
|
# A server will call newState() with the updated set state tree if `state` was set to `true`
|
||
|
# in `register`, however unless the last call to newState() didn't complete yet, as to not
|
||
|
# overload a client.
|
||
|
|
||
|
# TODO: There should probably be a more efficient approach here too, something along the
|
||
|
# lines of server-side filtering.
|
||
|
|
||
|
# TODO: Add newApplied?
|
||
|
}
|
||
|
}
|
||
|
|
||
|
interface Interestable {
|
||
|
interest @0 () -> ( interest: Interest );
|
||
|
}
|
||
|
|
||
|
interface Interest extends(Access) {
|
||
|
register @0 ( cb: Callback );
|
||
|
unregister @1 ();
|
||
|
|
||
|
blocking @2 ();
|
||
|
# As an alternative to the `register`/`Callback` system you can also call `blocking` which will
|
||
|
# — as the name suggests — block until the last claim was dropped.
|
||
|
|
||
|
interface Callback {
|
||
|
drop @0 ();
|
||
|
# The last claim on the resource this Interest is registered on was dropped, invalidating
|
||
|
# the Interest.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
interface Claimable {
|
||
|
# Having this capability set (i.e. not be a `nullptr`) means the user has at least writeable
|
||
|
# access to a resource and the resource is claimable (n > 0).
|
||
|
|
||
|
claim @0 () -> ClaimResponse;
|
||
|
# Assert a claim on a resource.
|
||
|
}
|
||
|
|
||
|
interface Lockable {
|
||
|
# Having this capability set means the user has managerial access to a resource.
|
||
|
|
||
|
lock @0 () -> ( lock: Claim );
|
||
|
}
|
||
|
|
||
|
struct ClaimResponse {
|
||
|
enum Error {
|
||
|
# Error describing why a claim failed.
|
||
|
|
||
|
exhausted @0;
|
||
|
# There are no more free Claim slots
|
||
|
|
||
|
locked @1;
|
||
|
# The resource was locked
|
||
|
}
|
||
|
|
||
|
union {
|
||
|
error @0 :Error;
|
||
|
success @1 :Claim;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
interface Claim extends(Access) {
|
||
|
# TODO: extend Persistance. Claims and Interests need to be able to survive a connection loss,
|
||
|
# which is exactly what `SturdyRef`/Persistance are designed to provide. The Persistance
|
||
|
# interface only provides one method, `save`, returning a `SturdyRef`. A SturdyRef is a generic
|
||
|
# and generally speaking opaque type that can be restored to a live capability using some sort
|
||
|
# of `Restorer` service.
|
||
|
# In this case the `Restorer` service could be `Claimable` / `Interestable` providing a
|
||
|
# `restore( ref: SturdyRef )` method.
|
||
|
|
||
|
update @0 Node;
|
||
|
# Update the State of the claimed resource with the given one
|
||
|
}
|
||
|
|
||
|
struct Resource {
|
||
|
name @0 :Text;
|
||
|
description @1 :Text;
|
||
|
typeid @2 :Text;
|
||
|
|
||
|
notify @3 :Notify;
|
||
|
interest @4 :Interest;
|
||
|
claimable @5 :Claimable;
|
||
|
lockable @6 :Lockable;
|
||
|
}
|