|
1 | | -# lightbug_api showcase |
| 1 | +# lightbug_api showcase — metaprogramming ergonomics demo |
2 | 2 | # ───────────────────────────────────────────────────────────────────────────── |
3 | 3 | # Run: mojo lightbug.mojo |
4 | 4 | # Test: curl http://localhost:8080/ |
|
13 | 13 | # -d '{"name":"Updated","price":19.99}' |
14 | 14 | # curl -X DELETE http://localhost:8080/items/42 |
15 | 15 | # curl http://localhost:8080/v1/status |
| 16 | +# curl http://localhost:8080/notes |
| 17 | +# curl http://localhost:8080/notes/1 |
16 | 18 | # ───────────────────────────────────────────────────────────────────────────── |
17 | 19 |
|
18 | | -from lightbug_api import App, GET, POST, PUT, DELETE, mount, HandlerResponse |
| 20 | +from lightbug_api import App, GET, POST, PUT, DELETE, mount, HandlerResponse, Resource, resource |
19 | 21 | from lightbug_api.context import Context |
20 | 22 | from lightbug_api.response import Response |
21 | 23 | from lightbug_http.http.json import JsonSerializable, JsonDeserializable |
@@ -55,62 +57,117 @@ struct StatusResponse(JsonSerializable, Movable, Defaultable): |
55 | 57 | self.version = "" |
56 | 58 |
|
57 | 59 |
|
| 60 | +@fieldwise_init |
| 61 | +struct Note(JsonSerializable, Movable, Defaultable): |
| 62 | + var id: Int |
| 63 | + var text: String |
| 64 | + |
| 65 | + fn __init__(out self): |
| 66 | + self.id = 0 |
| 67 | + self.text = "" |
| 68 | + |
| 69 | + |
58 | 70 | # ------------------------------------------------------------------ handlers |
| 71 | +# Handlers that need full control (non-200 status, redirects, plain text) keep |
| 72 | +# the HandlerResponse return type. Handlers that just return a model use the |
| 73 | +# model type directly — the framework auto-serialises as JSON 200 OK. |
59 | 74 |
|
60 | 75 | fn index(ctx: Context) raises -> HandlerResponse: |
61 | 76 | return Response.text("Welcome to lightbug_api 🔥") |
62 | 77 |
|
63 | 78 |
|
64 | | -fn list_items(ctx: Context) raises -> HandlerResponse: |
65 | | - return Response.json(Item(1, "Widget", 9.99)) |
| 79 | +# ── Before (old style) ─────────────────────────────────────────────────────── |
| 80 | + |
| 81 | +fn list_items(ctx: Context) raises -> Item: # ← returns Item directly |
| 82 | + return Item(1, "Widget", 9.99) |
66 | 83 |
|
67 | 84 |
|
68 | | -fn get_item(ctx: Context) raises -> HandlerResponse: |
69 | | - # ctx.param("id", 0) → Int (path param, typed by default value) |
70 | | - # ctx.query("verbose", False) → Bool (query param, typed by default value) |
| 85 | +fn get_item(ctx: Context) raises -> Item: # ← returns Item directly |
71 | 86 | var id = ctx.param("id", 0) |
72 | 87 | var verbose = ctx.query("verbose", False) |
73 | | - |
74 | 88 | if verbose: |
75 | 89 | print("GET /items/", id, " (verbose mode)") |
76 | | - |
77 | | - return Response.json(Item(id, String("Item ", id), 9.99)) |
| 90 | + return Item(id, String("Item ", id), 9.99) |
78 | 91 |
|
79 | 92 |
|
80 | 93 | fn create_item(ctx: Context) raises -> HandlerResponse: |
| 94 | + # Still HandlerResponse — needs 201 Created status code |
81 | 95 | var body = ctx.json[CreateItemRequest]() |
82 | 96 | var created = Item(100, body.name, body.price) |
83 | 97 | return Response.created(created) |
84 | 98 |
|
85 | 99 |
|
86 | | -fn update_item(ctx: Context) raises -> HandlerResponse: |
| 100 | +fn update_item(ctx: Context) raises -> Item: # ← returns Item directly |
87 | 101 | var body = ctx.json[CreateItemRequest]() |
88 | 102 | var id = ctx.param("id", 0) |
89 | | - return Response.json(Item(id, body.name, body.price)) |
| 103 | + return Item(id, body.name, body.price) |
90 | 104 |
|
91 | 105 |
|
92 | 106 | fn delete_item(ctx: Context) raises -> HandlerResponse: |
| 107 | + # Still HandlerResponse — needs 204 No Content |
93 | 108 | var id = ctx.param("id", 0) |
94 | 109 | print("Deleting item", id) |
95 | 110 | return Response.no_content() |
96 | 111 |
|
97 | 112 |
|
98 | | -fn health(ctx: Context) raises -> HandlerResponse: |
99 | | - return Response.json(StatusResponse("ok", "1.0.0")) |
| 113 | +fn health(ctx: Context) raises -> StatusResponse: # ← returns StatusResponse directly |
| 114 | + return StatusResponse("ok", "1.0.0") |
| 115 | + |
| 116 | + |
| 117 | +# ── Resource / controller pattern ──────────────────────────────────────────── |
| 118 | +# Group CRUD handlers in a struct; `resource[Notes]("notes")` registers all |
| 119 | +# five standard routes under /notes in one call. |
| 120 | + |
| 121 | +struct Notes(Resource): |
| 122 | + @staticmethod |
| 123 | + fn index(ctx: Context) raises -> HandlerResponse: |
| 124 | + return Response.json(Note(0, "all notes")) |
| 125 | + |
| 126 | + @staticmethod |
| 127 | + fn show(ctx: Context) raises -> HandlerResponse: |
| 128 | + var id = ctx.param("id", 0) |
| 129 | + return Response.json(Note(id, String("note ", id))) |
| 130 | + |
| 131 | + @staticmethod |
| 132 | + fn create(ctx: Context) raises -> HandlerResponse: |
| 133 | + return Response.created(Note(1, "new note")) |
| 134 | + |
| 135 | + @staticmethod |
| 136 | + fn update(ctx: Context) raises -> HandlerResponse: |
| 137 | + var id = ctx.param("id", 0) |
| 138 | + return Response.json(Note(id, "updated")) |
| 139 | + |
| 140 | + @staticmethod |
| 141 | + fn destroy(ctx: Context) raises -> HandlerResponse: |
| 142 | + return Response.no_content() |
100 | 143 |
|
101 | 144 |
|
102 | 145 | # --------------------------------------------------------------------- main |
103 | 146 |
|
104 | 147 | fn main() raises: |
105 | 148 | var app = App( |
| 149 | + # Plain-text response — HandlerResponse (unchanged) |
106 | 150 | GET("/", index), |
107 | | - GET("/items", list_items), |
108 | | - GET("/items/{id}", get_item), |
| 151 | + |
| 152 | + # Typed return — framework auto-serialises Item as JSON 200 OK |
| 153 | + GET[Item, list_items]("/items"), |
| 154 | + GET[Item, get_item]("/items/{id}"), |
| 155 | + |
| 156 | + # 201 Created — still HandlerResponse (needs explicit status) |
109 | 157 | POST("/items", create_item), |
110 | | - PUT("/items/{id}", update_item), |
| 158 | + |
| 159 | + # Typed return |
| 160 | + PUT[Item, update_item]("/items/{id}"), |
| 161 | + |
| 162 | + # 204 No Content — still HandlerResponse |
111 | 163 | DELETE("/items/{id}", delete_item), |
| 164 | + |
112 | 165 | mount("v1", |
113 | | - GET("status", health), |
| 166 | + # Typed return inside a mount |
| 167 | + GET[StatusResponse, health]("status"), |
114 | 168 | ), |
| 169 | + |
| 170 | + # Resource controller — five routes registered in one line |
| 171 | + resource[Notes]("notes"), |
115 | 172 | ) |
116 | 173 | app.run() |
0 commit comments