Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JMAP Core server implementation #4

Open
foxcpp opened this issue Sep 5, 2019 · 2 comments
Open

JMAP Core server implementation #4

foxcpp opened this issue Sep 5, 2019 · 2 comments
Labels
server Related to server code
Milestone

Comments

@foxcpp
Copy link
Owner

foxcpp commented Sep 5, 2019

Things to consider

Below are things I believe would make library more useful.

  • go-jmap should avoid enforcing certain storage design and definitely must not provide storage implementation itself. I think it is best to simply translate API requests into corresponding method calls on some interface called "Backend" (see go-imap for a good example of how this could be done).

  • go-jmap should avoid enforcing certain authentication design and definitely must not provide authentication implementation itself. As with previous point, authentication should be implemented by calling out into user-provided interface implementation.

  • go-jmap should rely only on net/http for HTTP server. Use of more advanced frameworks will make it easier to violate the first point.

  • JMAP Core, JMAP Mail, etc implementations should be kept independent of each other for extensibility purposes. JMAP is not only about email.

@foxcpp foxcpp added the server Related to server code label Sep 5, 2019
@foxcpp foxcpp added this to the go-jmap 0.1 milestone Sep 5, 2019
@foxcpp
Copy link
Owner Author

foxcpp commented Sep 5, 2019

Here is some basic outline for the sake of discussion:

JMAP Core server logic:

// implements http.Handler so it could be simply attached to /.well-known/jmap endpoint on net/http server.
type Server struct {}

type SessionManager interface {
  // value is the value of Authentication header field.
  CheckAuth(value string) (bool, error)
  Accounts(value string) (allAccts map[ID]Account, primaryAccts map[string]ID, err error)
}

type FuncHandler func(Invocation) (Invocation, error)

type CapabilityBackend struct {
  Handlers map[string]FuncHandler
  ArgUnmarshallers map[string]FuncArgsUnmarshal
}

func NewServer(auth SessionManager, cb ...CapabilityBackend) (Server, error) {}

Here is the typical flow:

  1. Server receives the HTTP request. Authentication header is checked using SessionManager object. Request is then deserialized from JSON, inner Invocation objects are deserialized using callbacks provided by CapabilityBackend's. Additionally, JSON Pointers ("back references") are resolved at this point too (I have no thoughts on how to implement it though, any ideas are welcome).
    Side note: This way we free backend implementations from having to implement JSON-related boilerplate. They receive arguments as concrete structures.
  2. Each Invocation is passed to callbacks provided by CapabilityBackend's. Returned Invocations are added to the Response object and then serialized to JSON and returned as HTTP response.

JMAP Mail, JMAP Calendar, etc implementations provide factory functions that return CapabilityBackend with handlers bound to user-provided backend (database) implementation.

Here is the example of how hypothetical JMAP Todo implementation would look like (some parts are left out for brevity):

// Implementation is provided by user.
type Backend interface {
  Query(args *TodoQueryArgs) (*TodoQueryResp, error)
}

func NewTodoServer(be Backend) jmap.CapabilityBackend {
  return jmap.CapabilityBackend {
    Handlers: map[string]jmap.FuncHandler{
      "Todo/query": func(i jmap.Invocation) (Invocation, error) {
        resp, err := be.Query(i.Args.(*TodoQueryArgs))
        return Invocation{Name: i.Name, CallID: i.Name, Args: resp}, err
      },
    },
    Unmarshallers: map[string]jmap.FuncArgsUnmarshal{
      "Todo/query": unmarshalTodoQueryArgs,
    },
  },
}

@foxcpp
Copy link
Owner Author

foxcpp commented Sep 5, 2019

Here is the example of main function implementation using interfaces proposed above:

func main() {
  sessionMngr := SessionThingy{} // perhaps something OAuth-based, whatever
  todoBackend := TodoBackend{} // Our implementation of Todo DB.

  jmapSrv := jmap.NewServer(&sessionMngr, todo.NewTodoServer(todoBackend))
  http.Handle("/.well-known/jmap", &jmapSrv)
  return http.ListenAndServeTLS(...);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
server Related to server code
Projects
None yet
Development

No branches or pull requests

1 participant