soma.cell

CRUD JSON API over a persistent Map: notes with title + body

Returns Maps directly (auto-serialized to JSON) and 404 responses on miss. curl -X POST localhost:8080/api/notes -d '{"title":"hi","body":"world"}'

A Soma program from the web part of the verified corpus: 77 lines, cells Notes, NotesTest. It passes soma check with soma 3.2.4.

How it is built

The cell Notes declares 6 signals in its face: request(method: String, path: String, body: String) -> Map, create(data: Map) -> Map, read(id: String) -> Map, update(id: String, data: Map) -> Map, remove(id: String) -> Map and index() -> List; keeps 1 memory slot: notes (Map<String, Map>, persistent and consistent). Handlers: create (line 20), read (line 27), update (line 33), remove (line 43), index (line 49) and request (line 53).

The file also holds 1 test cell (NotesTest) with 7 assertions, run by soma test.

Generated from soma describe on this file.

The source

soma.toml

[package]
name = "web-corpus"
version = "0.1.0"
entry = "todo_state.cell"

[verify]
cells = ["TodoService"]
deadlock_free = true
eventually = ["Done", "Cancelled"]

notes_crud.cell

// CRUD JSON API over a persistent Map: notes with title + body.
// Returns Maps directly (auto-serialized to JSON) and 404 responses on miss.
//   curl -X POST localhost:8080/api/notes -d '{"title":"hi","body":"world"}'

cell Notes {
    face {
        signal request(method: String, path: String, body: String) -> Map
        signal create(data: Map) -> Map
        signal read(id: String) -> Map
        signal update(id: String, data: Map) -> Map
        signal remove(id: String) -> Map
        signal index() -> List
        promise all_persistent
    }

    memory {
        notes: Map<String, Map> [persistent, consistent]
    }

    on create(data: Map) {
        let id = to_string(next_id())
        let note = map("id", id, "title", data.title, "body", data.body)
        notes.set(id, note)
        return note
    }

    on read(id: String) {
        let raw = notes.get(id)
        if raw == () { return response(404, map("error", "not found")) }
        return raw
    }

    on update(id: String, data: Map) {
        let raw = notes.get(id)
        if raw == () { return response(404, map("error", "not found")) }
        let title = data.title ?? raw.title
        let body = data.body ?? raw.body
        let note = map("id", id, "title", title, "body", body)
        notes.set(id, note)
        return note
    }

    on remove(id: String) {
        let existed = notes.delete(id)
        if existed == false { return response(404, map("error", "not found")) }
        return map("deleted", id)
    }

    on index() {
        return notes.values
    }

    on request(method: String, path: String, body: String) {
        let req = map("method", method, "path", path)
        match req {
            {method: "GET",    path: "/api/notes"}        -> index()
            {method: "POST",   path: "/api/notes"}        -> create(from_json(body))
            {method: "GET",    path: "/api/notes/" + id}  -> read(id)
            {method: "PUT",    path: "/api/notes/" + id}  -> update(id, from_json(body))
            {method: "DELETE", path: "/api/notes/" + id}  -> remove(id)
            _                                             -> response(404, map("error", "no route"))
        }
    }
}

cell test NotesTest {
    rules {
        assert create(map("title", "a", "body", "one")).title == "a"
        assert create(map("title", "b", "body", "two")).id == "2"
        assert read("1").body == "one"
        assert update("1", map("title", "z")).title == "z"
        assert update("1", map("title", "z")).body == "one"
        assert remove("2").deleted == "2"
        assert len(index()) == 1
    }
}

What the toolchain says

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ soma check notes_crud.cell
✓ All checks passed.
$ soma test notes_crud.cell
test NotesTest ...
  ✓ assert create(map("title", "a", "body", "one")).title == "a"
  ✓ assert create(map("title", "b", "body", "two")).id == "2"
  ✓ assert read("1").body == "one"
  ✓ assert update("1", map("title", "z")).title == "z"
  ✓ assert update("1", map("title", "z")).body == "one"
  ✓ assert remove("2").deleted == "2"
  ✓ assert len(index()) == 1

7 tests: 7 passed, 0 failed
$ soma verify notes_crud.cell
Verifying notes_crud.cell...
No state machine in this program: nothing to prove beyond `soma check` (invariants and lifecycles are what verify proves).
note: the soma.toml beside this file declares [verify] properties; none applies here (no state machine)
VERIFY OK (vacuous — no state machine)

Builtins used

from_json, len, map, next_id, response, to_string

Source file: /corpus/web/notes_crud.cell · examples/corpus/web/notes_crud.cell