Fan

 

UriSpaces

Overview

WARNING: the UriSpace subsystem is still evolving and will likely have design changes as it gets flushed out

The basics of Uri naming are covered in the Naming chapter. This chapter covers the APIs related to managing "fan:" uris.

Fan defines a standard URI naming system influenced by the REST architecture of the World Wide Web:

  • Resources are represented as Fan objects
  • Resources are identified with Uris
  • Resources are managed using a small set of "verbs"
  • Resources are transfered between threads safely

The sys::UriSpace class defines a namespace of Uri to resource mappings with a small set of CRUD methods:

Fan's uri space is managed much like a Unix file system. The uri space is managed as a tree of Uri paths. By default the root uri space manages everything under "/". The root uri space implements its CRUD interface using a simple memory store. Alternate uri space implementations can be mounted to handle branches in the Uri path tree using UriSpace.mount.

Root UriSpace

By default all Uris within the uri space are managed by the root uri space. The root uri space provides a thread safe memory backing store for resources. Example:

UriSpace.root.create(`/hello`, "hi")
UriSpace.root[`/hello`]               => "hi"
UriSpace.root.put(`/hello`, "hola")
UriSpace.root[`/hello`]               => "hola"
UriSpace.root.delete(`/hello`)
UriSpace.root[`/hello`]               => raises UnresolvedErr
UriSpace.root.get(`/hello`, false)    => null

In this example the uri space is really just a fancy Uri to Obj map. But under the covers the uri space is doing special work to ensure that everything is thread safe. This allows threads to share information safely - this design pattern is sometimes called a whiteboard.

In order to ensure thread safety the resources managed by the root uri space must be either immutable or serializable. If the resource isn't immutable, then what you read out is a copy of the object - changes to it aren't seen by other threads until it is written back via put. Consider this example:

UriSpace.root.create(`/var`, [0, 1, 2])
var := UriSpace.root[`/var`]   =>  [0, 1, 2]     // local copy made
var.add(3)                                       // local copy is changed
UriSpace.root[`/var`]          =>  [0, 1, 2]     // ns copy is unchanged
UriSpace.root.put(`/var`, var)                   // save local back to ns
UriSpace.root[`/var`]          =>  [0, 1, 2, 3]  // ns copy is now changed

Custom UriSpaces

You can create your own subclasses of sys::UriSpace to create custom handlers for mapping Uris to resources. For example this is how you typically manage navigation of your data model in flux and web applications.

The only method you are required to override is get; most other methods will provide a default implementation which throws UnsupportedErr. For example to create a uri space which returns the current time:

const class TimeUriSpace : UriSpace
{
  override Obj? get(Uri uri, Bool checked := true)
  {
    uri = uri.relTo(this.uri)
    if (uri.path.isEmpty) return DateTime.now
    else return DateTime.now.trap(uri.path[0], [,])
  }
}

UriSpace.mount(`/time`, TimeUriSpace())
echo(UriSpace.root[`/time`])        => 2008-04-07T21:47:21.078-04:00 New_York
echo(UriSpace.root[`/time/year`])   => 2008
echo(UriSpace.root[`/time/month`])  => apr

Note the first line of code calles Uri.relTo. We mount the TimeUriSpace under "/time" - which means it will handle all Uris which begin with "/time". The uri passed into get is absolute - so to make it flexible where TimeUriSpace is mounted, we always relativize to the uri of the space itself.

Dir UriSpaces

The factory method UriSpace.makeDir creates a custom uri space instance which allows you to mount a file system directory into the Fan uri space:

UriSpace.mount(`/fandocs`, UriSpace.makeDir(Repo.boot.home +`doc/`))
UriSpace.root[`/fandocs/`]            =>  sys::File for doc directory
UriSpace.root[`/fandocs/index.html`]  =>  sys::File for doc/index.html

Mounting

When a custom uri space is mounted via UriSpace.mount it assumes responsibility for all the Uri paths under it. If no custom uri spaces are responsible for a given Uri, then responsbility always defaults to the root uri space. You can query which UriSpace instance is responsible for a given Uri using the UriSpace.find method:

UriSpace.mount(`/apple`, x)
UriSpace.mount(`/a`,     y)
UriSpace.mount(`/a/b/c`, z)

UriSpace.find(`/app`)        => sys::RootUriSpace
UriSpace.find(`/apple`)      => x
UriSpace.find(`/a`)          => y
UriSpace.find(`/a/b`)        => y
UriSpace.find(`/a/b/chump`)  => y
UriSpace.find(`/a/b/c`)      => z
UriSpace.find(`/a/b/c/d`)    => z