soma.cell

URL shortener: POST a long URL, get a short code; GET the code redirects

Base36 code derived from a monotonic id. Hit count tracked per code. curl -X POST localhost:8080/shorten -d '{"url":"https://soma.dev"}' curl -i localhost:8080/r/<code>

A Soma program from the web part of the verified corpus: 75 lines, cells Shortener, ShortenerTest. It passes soma check with soma 3.2.4.

How it is built

The cell Shortener declares 4 signals in its face: request(method: String, path: String, body: String) -> Map, shorten(url: String) -> Map, resolve(code: String) -> String and stats(code: String) -> Map; keeps 2 memory slots: urls (Map<String, String>, persistent and consistent) and hits (Map<String, Int>, persistent and consistent). Handlers: shorten (line 33), resolve (line 40), stats (line 47) and request (line 52). Private helpers: _encode.

The file also holds 1 test cell (ShortenerTest) with 5 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"]

url_shortener.cell

// URL shortener: POST a long URL, get a short code; GET the code redirects.
// Base36 code derived from a monotonic id. Hit count tracked per code.
//   curl -X POST localhost:8080/shorten -d '{"url":"https://soma.dev"}'
//   curl -i localhost:8080/r/<code>

cell Shortener {
    face {
        signal request(method: String, path: String, body: String) -> Map
        signal shorten(url: String) -> Map
        signal resolve(code: String) -> String
        signal stats(code: String) -> Map
        promise all_persistent
    }

    memory {
        urls:  Map<String, String> [persistent, consistent]
        hits:  Map<String, Int>    [persistent, consistent]
    }

    on _encode(n: Int) {
        let alpha = "0123456789abcdefghijklmnopqrstuvwxyz"
        if n == 0 { return "0" }
        let s = ""
        let x = n
        while x > 0 {
            let d = x - floor(x / 36) * 36
            s = concat(alpha[d], s)
            x = floor(x / 36)
        }
        return s
    }

    on shorten(url: String) {
        let code = _encode(next_id())
        urls.set(code, url)
        hits.set(code, 0)
        return map("code", code, "url", url)
    }

    on resolve(code: String) {
        let url = urls.get(code)
        if url == () { return "" }
        hits.set(code, hits.get(code) + 1)
        return url
    }

    on stats(code: String) {
        if urls.get(code) == () { return response(404, map("error", "unknown code")) }
        return map("code", code, "url", urls.get(code), "hits", hits.get(code))
    }

    on request(method: String, path: String, body: String) {
        let req = map("method", method, "path", path)
        match req {
            {method: "POST", path: "/shorten"}       -> shorten(from_json(body).url)
            {method: "GET",  path: "/r/" + code}     -> {
                let url = resolve(code)
                if url == "" { response(404, map("error", "unknown code")) } else { redirect(url) }
            }
            {method: "GET",  path: "/stats/" + code} -> stats(code)
            _                                        -> response(404, map("error", "no route"))
        }
    }
}

cell test ShortenerTest {
    rules {
        assert _encode(0) == "0"
        assert _encode(35) == "z"
        assert _encode(36) == "10"
        assert shorten("https://soma.dev").url == "https://soma.dev"
        assert resolve(shorten("https://x.io").code) == "https://x.io"
    }
}

What the toolchain says

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

$ soma check url_shortener.cell
warning: `s` grows by concatenation inside a loop — each step copies the whole value, so the loop is quadratic (8 MB took 32 s, a list + join() 30 ms): push the pieces to a List and `join(parts, "")` once
  --> url_shortener.cell:27:13
   |
27 |             s = concat(alpha[d], s)
   |             ^
✓ 1 warning(s), no errors.
$ soma test url_shortener.cell
test ShortenerTest ...
  ✓ assert _encode(0) == "0"
  ✓ assert _encode(35) == "z"
  ✓ assert _encode(36) == "10"
  ✓ assert shorten("https://soma.dev").url == "https://soma.dev"
  ✓ assert resolve(shorten("https://x.io").code) == "https://x.io"

5 tests: 5 passed, 0 failed
$ soma verify url_shortener.cell
Verifying url_shortener.cell...
note: soma.toml [verify] cells names 'TodoService', which this file does not define — its properties are not checked here

Shortener/termination:

  ⚠ handler `_encode`: while-loop without provable termination bound (write `while [loop_bound(N)] cond { … }`, or a for-loop over a bounded range)
  ⚠ `shorten` calls `Shortener._encode`, which may not terminate
  ⚠ `request` calls `Shortener._encode`, which may not terminate

0 passed, 3 warnings, 0 failures
soma.toml: 1 user-defined properties loaded — NONE applies to this file ([verify] cells = ["TodoService"] names no cell of it)
Temporal: 0 passed, 0 failed
VERIFY OK

Builtins used

concat, floor, from_json, map, next_id, redirect, response

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