Compare commits

..

266 Commits

Author SHA1 Message Date
Greg Johnston
b47f492a32 chore: new clippy warnings 2024-04-01 14:35:42 -04:00
Greg Johnston
40e1fd5024 chore: new clippy warnings 2024-04-01 14:04:28 -04:00
Greg Johnston
436cd1ed2e update macro tests with new output 2024-04-01 14:03:02 -04:00
Greg Johnston
d5ab1cc0e2 fix rkyv issue on more recent nightly 2024-04-01 12:11:54 -04:00
Greg Johnston
9408a9ecd2 use earlier nightly for non-example CI 2024-04-01 12:02:17 -04:00
Greg Johnston
cf15eb6ec8 chore: add wasm-pack for Deno/js-fetch example 2024-04-01 10:54:28 -04:00
Greg Johnston
3c126e4211 chore: new clippy warnings 2024-04-01 10:53:27 -04:00
Greg Johnston
b420579c91 chore: fix example nightly version 2024-04-01 10:45:34 -04:00
Greg Johnston
bec0420155 chore: ignore rkyv in CI 2024-04-01 10:20:48 -04:00
Greg Johnston
9153516f4e chore: new clippy warnings 2024-04-01 09:11:11 -04:00
Greg Johnston
8fa3625a0f simulate change in core 2024-04-01 09:01:09 -04:00
Greg Johnston
aadceb72ac simulate change in core 2024-04-01 09:00:55 -04:00
Greg Johnston
d1d30ae55d chore: new clippy warnings 2024-04-01 09:00:42 -04:00
Greg Johnston
3a2fd29fdb chore: bump nightly version in CI 2024-04-01 08:24:21 -04:00
Greg Johnston
8e7a2e6a10 chore: bump nightly version in CI 2024-04-01 08:21:21 -04:00
Greg Johnston
e00b7fc135 chore: new clippy warnings 2024-04-01 08:19:10 -04:00
Greg Johnston
7683420c03 chore: new clippy warnings 2024-03-31 20:59:35 -04:00
Greg Johnston
58b22ec2d6 chore: bump nightly version in examples 2024-03-31 14:13:53 -04:00
Greg Johnston
bfac4cba2a chore: cargo fmt 2024-03-31 14:12:33 -04:00
Paolo Barbolini
3e18edb8f9 chore: add repository field to server_fn_macro (#2474) 2024-03-31 14:10:47 -04:00
Joseph Cruz
e926ff24a6 ci: disable semver checks (#2471) 2024-03-30 20:05:20 +00:00
Gunnar Raßmann
d528cbd828 Fix: Environment variables do not overwrite Config.toml options (#2433)
* Fix environment variable parsing

* Fix failing tests

dfgdfgfd

dsf

* Add new test
2024-03-30 00:02:52 +00:00
Alex Lazar
642504f2ba Remove panic for axum ResponseOptions (#2468) 2024-03-29 07:37:12 +00:00
zakstucke
fd2817de26 Allow CDN_PKG_PATH at runtime as well as current build time, preferring it when available. (#2466) 2024-03-28 08:30:54 +00:00
Bart Toersche
73b8c7872e Fix: Small fix for location hash/fragment (#2464) 2024-03-27 06:45:29 +00:00
martin frances
f3d19ca744 Minor: Ran cargo clippy --fix (#2461)
Manually reviewed the changes. All look like reasonable nudges.

A summary :-

In one place removed a redundant call to .clone().

In two places, now using clone_from() which clippy says
**MAY** be an optimisation.
2024-03-23 18:27:31 -07:00
boyswan
0abcc348ca Persist parent span context within resource fetchers (#2456) 2024-03-22 15:51:50 -07:00
Joseph Cruz
572ae5bbdf test(ci): check semver (#2450)
* test(ci): check semver

* chore: simulate change

* fix(ci): add checkout

* fix(ci): version typo

* chore: remove simulated change
2024-03-22 15:51:13 -07:00
martin frances
0b70949118 Bumped base64 to 0.22. (#2457) 2024-03-22 15:07:04 -07:00
martin frances
5819014ccc Chore(ci) bumping tj-actions/changed-files to version 43. (#2454) 2024-03-22 07:23:57 -04:00
Joseph Cruz
630fd4570d fix(ci): trunk command not found (#2453)
* chore: simulate change

* chore: remove print trunk version

* Revert "chore: remove print trunk version"

This reverts commit c203a83b44.

* chore(ci): use jetli/trunk-action

* chore: remove simulated change
2024-03-22 07:23:48 -04:00
Ratul
d1560f9e1f Added missing link for #[server] macro (#2437)
* Added missing link for #[server] macro

Added missing link for #[server] macro

* Removed spurious entry
2024-03-20 14:24:54 -07:00
martin frances
841d7a690a chore: examples/tailwind_axum bumped tailwindcss to 3.4.2. (#2443) 2024-03-19 09:40:24 -07:00
sify21
104c09f3bf register server_fn first to allow for wildcard Route path (#2435)
It's normal to have a `NotFound` page with a wildcard path like this
```
<Routes>
    ...
    <Route path="*any" view=NotFound>
</Routes>
```
In `ssr` mode, most servers do a `first match win` approach, so we
should register server functions before view routes, or else a wildcard
route would block all api requests.

https://discord.com/channels/1031524867910148188/1218508054442545185

Signed-off-by: 司芳源 <sify21@163.com>
2024-03-19 09:37:42 -07:00
Joseph Cruz
ac75999c9f chore(ci): upgrade actions to node 20 (#2444)
* chore(ci): install jq with apt

* chore(ci): install trunk with cargo

* chore(ci): replace toolchain action

* chore(ci): upgrade pnpm cache action

* chore: simulate change

* fix(ci): pnpm cache action typo

* chore: remove simulated change
2024-03-19 09:36:30 -07:00
Richard Laughlin
7ef186f642 For the session_auth_axum example, move the passhash into a separate (#2446)
non-serializable struct.

This prevents it from being returned in the
get_user() API, and prevents it from being unintentionally returned on any
new API the end-user may create on top of this example code.
2024-03-19 09:35:53 -07:00
Joseph Cruz
fda4dba237 build(examples): clean more output (#2420)
* chore(examples): update workspace members

* build(examples): clean e2e crates

* build(examples): clean pkg directories

* chore: remove simulated change comment

* chore: add simulated change

* chore: remove simulated change
2024-03-18 11:58:37 -04:00
Roland Fredenhagen
4e578e335b chore: update attribute-derive (#2438) 2024-03-18 11:39:34 -04:00
Joseph Cruz
97fd8ff6c4 fix(ci): leptos examples fail with bindgen schema error (#2428) 2024-03-13 22:33:54 -04:00
battmdpkq
4faf3fa894 chore: fix types in some comments (#2413)
Signed-off-by: battmdpkq <cmaker@163.com>
2024-03-09 07:38:25 -05:00
Greg Johnston
480d741749 chore: update to gloo-net 0.5 (closes #2411) (#2416) 2024-03-08 15:22:12 -05:00
Álvaro Mondéjar
7928f61401 chore: add lint to disallow prints to stdout (#2404) 2024-03-08 13:18:37 -05:00
Giovanni
2b4f5e0f58 docs: runtime error if setting the same event listener 2x rather than silent failure (#2383)
Delegated event listeners do not support adding more than one event listener of the same type. This can cause confusion if two listeners are added, as one is silently dropped.
2024-03-07 16:49:23 -05:00
Giovanni
943a992570 fix: re-throw errors in delegated event listeners (#2382) 2024-03-07 16:48:21 -05:00
ARSON
372a241d78 feat: allow #[prop(attrs)] on slots (#2396) 2024-03-04 17:34:21 -08:00
Chris Biscardi
c06f6bede2 fix: remove erroneous debug println!()s in islands (#2402) 2024-03-04 06:56:18 -05:00
benwis
3e93a686f4 Fix and release deps 2024-03-03 17:04:34 -08:00
benwis
34cdff4cb3 Update deps in one crate to 0.6.8 2024-03-03 17:02:50 -08:00
John Lewis
530087d77d Add MessagePack codec (#2371)
* feat: added messagepack codec

* fix: deserialize msgpack from bytes, not string
2024-03-03 13:54:23 -08:00
martin frances
4bb43f6207 examples/todomvc - Rename Todos::new() Todos::default(). (#2390) 2024-03-03 13:48:40 -08:00
benwis
9e2fb62857 0.6.8 2024-03-02 18:01:10 -08:00
Ben Wishovich
1da2fff706 Fix missed stuff (#2398) 2024-03-02 17:57:20 -08:00
Greg Johnston
9fd2987447 fix: correctly reset hydration status in islands mode Suspense (closes #2332) (#2393) 2024-03-02 11:57:35 -05:00
zroug
7996f835d0 fix: remove unnecessary trait bound PartialEq from create_owning_memo (#2394) 2024-03-02 07:27:22 -05:00
Greg Johnston
d72b12524e Merge pull request #2395 from leptos-rs/int-ax-doc 2024-03-01 20:08:18 -05:00
Greg Johnston
8e79c5be5c fix: ignore as with other doctests for now 2024-03-01 18:39:55 -05:00
Greg Johnston
de25658c36 Merge pull request #2392 from paul-hansen/fix-ci
fix(ci): "needless borrow" error and example never exiting
2024-03-01 18:37:48 -05:00
Paul Hansen
e2e35a9659 fix(ci) Wait a bit longer for server to start
It took longer than I thought in Github and barely worked, giving it a
bit more of a buffer.
2024-03-01 15:47:59 -06:00
Paul Hansen
bf1ba589c5 fix(ci): Another attempt to fix hanging example 2024-03-01 15:41:22 -06:00
Sam Judelson
f70ebc1289 docs: add note on how to get ResponseOptions (#2380) 2024-03-01 10:47:02 -05:00
Paul Hansen
3cab09e015 fix(ci): error_boundary example never exiting 2024-03-01 09:21:58 -06:00
Paul Hansen
b431315f7c fix(ci): "needless borrow" error 2024-03-01 09:21:58 -06:00
Baptiste
5b40881e77 fix: specify path to wasm_bindgen in island macro (#2387) 2024-03-01 10:15:19 -05:00
benwis
59d3cce3be 0.6.7 2024-02-29 13:38:09 -08:00
Paul Hansen
6a83161368 chore: add MSRV (#2360) 2024-02-28 07:19:09 -05:00
Marc-Stefan Cassola
4b00c16cb9 added hashes generated from cargo-leptos (#2373)
* added hashes generated from cargo-leptos

* Added config option to disable frontend file name hash
2024-02-27 16:28:27 -08:00
haslersn
6d6019b956 fix: do not strip query in redirect hook when using client-side navigation (#2376) 2024-02-27 09:06:48 -08:00
Sam Judelson
3540291065 docs: Resource::read() in doc examples with Resource::get() (#2372) 2024-02-26 21:37:29 -05:00
zoomiti
4809cf473e feat: provide leptos_router::Method via context (#1808) (#2315) 2024-02-26 21:25:53 -05:00
Tadas Dailyda
aa977001c1 feat: add support for trailing slashes (closes #2154) (#2217) 2024-02-26 20:56:44 -05:00
Greg Johnston
c16189f095 Merge pull request #2362 from leptos-rs/remove-deprecation
chore(ci): fix failing CI by removing deprecation note
2024-02-24 13:05:02 -05:00
Greg Johnston
d0a013c248 chore(ci): omit a few feature flag in CI 2024-02-24 11:55:36 -05:00
Greg Johnston
531ea74e33 chore: cargo fmt in leptos_macro 2024-02-24 07:12:46 -05:00
Greg Johnston
545e87e540 chore(ci): fix failing CI by removing deprecation note 2024-02-24 07:07:31 -05:00
Joseph Cruz
2ca30a0b2d ci(examples): build hackernews_js_fetch with deno (#2344)
* ci(examples): refactor process management

* ci(examples): build hackernews_js_fetch with deno

* ci(workflows): detect hackernews_js_fetch change

* chore(web-report): report deno usage

* chore(web-report): ignore gtk example

* ci(todo_app_sqlite): simulate change

* ci(workflows): install deno

* ci(todo_app_sqlite): remove simulated change
2024-02-23 13:40:44 -05:00
zoomiti
753bf1ed54 Fix Broken Doc links and Deprecate FromUtf8Error in oco.rs (#2318)
* fix: deprecate `FromUtf8Error` in `oco.rs`

* chore: fix broken doc links (#859)

* chore: fix broken doc link to server attribute macro

* cargo fmt
2024-02-21 19:24:40 -08:00
Sam Judelson
37c6387fea finish doc sentence (#2348) 2024-02-21 19:21:57 -08:00
Janu (Janeshwar) Cambrelen
0a73487152 feat(leptos-axum): propagate trace context to server functions (#2340) 2024-02-21 19:21:00 -08:00
Sam Judelson
747aba0d7f add comment specifying edgecase of server function prefixes (#2345) 2024-02-20 18:17:20 -08:00
Sam Judelson
04cf47d5da Update suspense_component.rs documentation to use .get() instead of .read() (#2346) 2024-02-20 18:16:03 -08:00
rjmac
47abe00993 fix: don't leak canceled timeouts (#2331)
Instead of using `Closure::once_into_js`, this uses `into_js_value`,
which uses weak references to clean up the closure when Javascript no
longer has need of it.

It would be nice to make this (and the similar interval function) drop
the callback promptly when cancelled, but I don't think that's
possible while keeping the handles Copy.

Fixes #2330

Co-authored-by: Robert Macomber <robertm@mox>
2024-02-19 21:17:26 -05:00
eliza
aa3700ffb9 feat: add impl_from argument to #[server] proc_macro (#2335)
* Add `impl_into` argument

* Add `impl_into` argument

* Revert unneeded changes

* Address review comments

Rename `impl_into` back to be `impl_from`
Rework docstring

* Fix typo in docstring
2024-02-19 21:16:46 -05:00
Aphek
0770b87cb7 feat: Add owning memos to allow memos that re-use the previous value (#2139) 2024-02-19 21:16:19 -05:00
benwis
330ebdb018 v0.6.6 2024-02-19 13:48:32 -08:00
benwis
c3179d88cf Moved leptos-spin-macro dep to released version 2024-02-19 12:54:43 -08:00
Sahaj
ffcf3c2952 example: fix href path in tailwind_csr example (#2328) 2024-02-17 13:10:07 -05:00
haslersn
001ca5148e fix: handle cross-origin redirects in server function redirect hook (#2329)
In client-side navigation we now handle redirects returned from
server functions by resolving the location against the current
origin as a base. The base is only relevant if the location
doesn't already include an origin. This fixes cross-origin
redirects.

Note: in order to handle redirects in the same way as the browser
would handle them, we need to use the server function's URL
(typically `<origin>/api/something`) as a base. I leave this as
a TODO for a future leptos version, because it probably
requires changing the signature of the `server_fn` redirect hook.

In order to not be affected by a future breaking change, users
should already start making sure that their redirect locations
either include an origin or at least start with a single slash
(e.g. `Location: /foo`).
2024-02-17 13:09:39 -05:00
Greg Johnston
1e000afa78 examples: fix CSS file name in tailwind_axum (#2324) 2024-02-17 12:56:03 -05:00
Greg Johnston
0f7b8841b2 chore(ci): reduce set of tested features to prevent running out of disk space in server_fn (#2320) 2024-02-16 20:26:26 -05:00
Greg Johnston
7dc0441f6c docs: log error on failing to convert form to ServerFn type, in addition to setting action value (#2319) 2024-02-16 17:11:14 -05:00
Joseph Cruz
0a321a1bd7 docs(examples): update docs (#2313)
* docs(examples): fix metadata typo

* docs(examples): update first step about using cargo make
2024-02-16 13:32:01 -05:00
Greg Johnston
88742952f0 fix: Transition in hydrate mode that isn't initially created (closes #2279) (#2314) 2024-02-16 08:16:09 -05:00
martin frances
8a4b972e0b chore: bump config to 0.14 (#2302) 2024-02-15 20:24:12 -05:00
zoomiti
95bd9cc544 feat: use CDN_PKG_PATH at build time to set alternate base URL for JS/WASM bundles (#2281) (#2283) 2024-02-15 20:21:47 -05:00
Marc-Stefan Cassola
23bc892a24 fix: #[server] macro error type detection (#2298)
In most cases when you return `Result<..., ServerFnError<E>>` this worked but when you tried
`Result<..., leptos::ServerFnError<E>>` then it didn't.
2024-02-15 20:20:41 -05:00
Esteban Borai
830fba794e docs: add missing provide_meta_context() in example (#2311)
Otherwise user gets:

```
use_head() is being called without a MetaContext being provided. We'll automatically create and provide one, but if this is being called in a child route it may cause bugs. To be safe, you should provide_meta_context() somewhere in the root of the app.
```
2024-02-15 20:19:07 -05:00
martin frances
98633c8700 chore(ci): update node version for GitHub Actions (#2303) 2024-02-15 20:17:12 -05:00
David Rebbe
b0f5c39711 example: replace yanked version of session_auth_axum crate (#2310) 2024-02-15 20:16:26 -05:00
Greg Johnston
b54aa7f3f5 Merge pull request #2294 from agilarity/add-cargo-make-leptos 2024-02-15 18:52:37 -05:00
Sam Judelson
e33ee7ec99 pub export server is either from leptos_macro or leptos_spin_macro depending on if spin feature is enabled. (#2280)
* leptos spin server macro

* leptos spin

* git chng

* based on the fermyon official git for when that works
2024-02-15 14:37:19 -08:00
Joseph Cruz
cd70b2f52b fix(ci): should exclude cargo-make 2024-02-11 20:40:20 -05:00
Joseph Cruz
c75842ed0c ci(hackernews_islands_axum): build with cargo leptos 2024-02-11 15:40:32 -05:00
Joseph Cruz
4ad228bf47 docs(test-report): add leptos ci warning 2024-02-11 15:40:32 -05:00
Joseph Cruz
bbe7115360 docs(test-report): mention options 2024-02-11 15:40:32 -05:00
Greg Johnston
0658a550b0 fix(examples): align crate name and output name (closes #2206) (#2291) 2024-02-10 15:47:25 -05:00
Joseph Cruz
4222c832b1 fix(ci): empty directory vector error (#2288)
* fix(ci): empty directory vector error

* chore(ci): simulate example change

* chore(ci): remove simulated example change
2024-02-10 10:02:21 -08:00
Greg Johnston
dfddbd6bf9 docs: give a warning when you try to .dispatch() an action immediately (closes #2225) (#2286) 2024-02-09 20:55:10 -05:00
Greg Johnston
8a77691cb5 Merge pull request #2285 from leptos-rs/fix-issues
Fix remaining CI issues
2024-02-09 19:29:01 -05:00
Greg Johnston
1dbe8b2d4b fix: correct feature name for server-fn-macro crate (broken in #2270) 2024-02-09 17:18:44 -05:00
Greg Johnston
fe64f0d332 examples: fix counter_isomorphic (broken in #2244) and fix additional warnings 2024-02-09 17:12:31 -05:00
Joseph Cruz
c00207aa46 fix(test-report) should show all cargo-make leptos configuration (#2282)
* refactor(test-report): extract script
* refactor(test-report): extract functions
* refactor(test-report): split option to tasks
* chore(test-report): highlight examples without tags
* fix(test-report): show all cargo-make leptos configuration
* docs(test-report): update keys
* chore(test-report): include all crates in report
2024-02-09 16:31:46 -05:00
Joseph Cruz
65b7603192 fix(ci): address clippy issue (#2278)
* fix(ci): address clippy issue
* fix(ci): add missing nightly specifications
* fix(ci):  set all nightly references
* chore(ci): do not lint example crates
2024-02-09 16:30:11 -05:00
haslersn
d4bdc36062 fix: add key/value pair from submit button when parsing form event (#2268) 2024-02-07 11:09:01 -05:00
Sam Judelson
1b55227d10 fix: remove unnecessary default features on axum in server_fns to support running Axum in a WASM environment (#2270) 2024-02-07 11:08:48 -05:00
Saikat Das
a903e19eb2 chore: fix typo (#2267) 2024-02-06 17:55:35 -05:00
blorbb
38bf73947f fix: make directive .into() calls consistent (#2249) 2024-02-05 08:52:12 -05:00
Greg Johnston
e4b89ba243 Merge pull request #2262 from leptos-rs/2261
fix: guarantee execution order of effects (closes #2261)
2024-02-05 08:51:23 -05:00
Greg Johnston
701e3077fb chore: cargo fmt 2024-02-05 06:38:02 -05:00
Greg Johnston
aec4d680aa fix: guarantee execution order of effects (closes #2261) 2024-02-05 06:35:57 -05:00
Steffen
06721c5fcd examples: fix typos in examples (#2260) 2024-02-05 05:20:53 -05:00
SleeplessOne1917
1ddb39e9bd docs: typo in actix integrations docs (#2258)
Co-authored-by: SleeplessOne1917 <insomnia-void@protonmail.com>
2024-02-04 20:57:51 -05:00
Chris
15d4ca0638 feat(axum): provide state to server fn context (#2257)
Note that this is a minimal implementation and will __not__ allow the
user to `expect_state` if they have external calls to rendering their
app (i.e. using `render_app_to_*` directly).
2024-02-04 19:26:21 -05:00
zoomiti
85c3755f6d fix: bug with percent decoding of url params (#2251) 2024-02-04 19:24:02 -05:00
Sam Judelson
66ea072bc0 docs: a note to HtmlElement<El> about Deref (#2218) 2024-02-04 15:34:39 -05:00
Joris Hartog
b0b3c21285 docs: fix broken link in leptos_router (#2256) 2024-02-04 15:29:34 -05:00
Greg Johnston
56088a9ead fix: error rather than panicking if unable to send response in Axum integration (#2241)
* fix: error rather than panicking if unable to send response in Axum integration
2024-02-03 19:18:41 -05:00
martin frances
69d25d9c63 examples/hackernews: Add a "Suspense" wrapper. (#2253)
This warning appears in the browser's console log.

```
hackernews.js:933 At src/routes/stories.rs:39:17, you are reading a resource in `hydrate` mode outside a <Suspense/> or <Transition/>. This can cause hydration mismatch errors and loses out on a significant performance optimization. To fix this issue, you can either:
1. Wrap the place where you read the resource in a <Suspense/> or <Transition/> component, or
2. Switch to using create_local_resource(), which will wait to load the resource until the app is hydrated on the client side. (This will have worse performance in most cases.)
```
2024-02-03 14:24:46 -08:00
martin frances
5029b8f315 Chore: Minor, ran ``cargo fmt`` (#2254) 2024-02-03 14:24:12 -08:00
martin frances
0cba7bc22b example/counter_isomorphic Removed console warning. (#2244)
This warning is seen in the browsers console window.

```
counter_isomorphic.js:1068 At src/counters.rs:138:17, you are reading a resource in `hydrate` mode outside a <Suspense/> or <Transition/>. This can cause hydration mismatch errors and loses out on a significant performance optimization. To fix this issue, you can either:
1. Wrap the place where you read the resource in a <Suspense/> or <Transition/> component, or
2. Switch to using create_local_resource(), which will wait to load the resource until the app is hydrated on the client side. (This will have worse performance in most cases.)
```

Two derived signals "value" and "error_msg" need to be wrapped in a <Suspense> block.

"value" falls back to just the initial text.
"error" uses the default fallback.
2024-02-01 16:50:15 -08:00
Michael Kadziela
fb97c50886 Update rkyv example button text to accurately reflect what it does (#2250) 2024-02-01 16:49:29 -08:00
Greg Johnston
f1bc734dcf 0.6.5 2024-01-31 19:40:41 -05:00
Greg Johnston
f71b4aae69 feat: easily create custom server fn clients (#2247) 2024-01-31 09:15:30 -05:00
Greg Johnston
a834c03974 fix: bug with Actix redirects (#2246) 2024-01-31 09:14:40 -05:00
Greg Johnston
595013579c 0.6.4 2024-01-30 09:17:52 -05:00
Greg Johnston
8b1bd1ae9e Merge pull request #2240 from leptos-rs/err-serialization
fix: serialization error during SSR on ServerFnError
2024-01-29 16:32:55 -05:00
Greg Johnston
6ef1531059 example: file upload with streaming progress bar (#2242) 2024-01-29 15:20:19 -05:00
Greg Johnston
9f1406250e chore: update deprecated .remove() method on IndexSet 2024-01-29 11:32:15 -05:00
Greg Johnston
1f6a892291 fix: serialization error during SSR on ServerFnError 2024-01-29 10:36:08 -05:00
Greg Johnston
0ff1e279a2 fix: correctly track source in create_local_resource (#2238) 2024-01-28 10:09:03 -08:00
Chris
c6096cc2a0 chore: define edtion = "2021" in rustfmt.toml (#2235) 2024-01-27 16:04:25 -08:00
Greg Johnston
8a2ae7fc7c \v0.6.3\ 2024-01-26 21:00:21 -05:00
Greg Johnston
9de34b74cf 0.6.2 2024-01-26 18:07:04 -05:00
Greg Johnston
1b5961edaa fix: fix type inference on extract() functions (#2233) 2024-01-26 17:54:42 -05:00
Greg Johnston
26d1aee9ad Update README.md framework comparisons (#2232) 2024-01-26 17:01:26 -05:00
benwis
2bf09384df 0.6.1
Signed-off-by: benwis <ben@celcyon.com>
2024-01-26 12:32:14 -08:00
benwis
ac12e1a411 0.6.0
Signed-off-by: benwis <ben@celcyon.com>
2024-01-26 11:54:07 -08:00
Greg Johnston
b367b68a43 fix: use #[server(default)] to pass use default values for a field (#2231) 2024-01-26 14:46:31 -05:00
Greg Johnston
1f9dad421f fix: allow paths to ServerFnError type (#2230) 2024-01-26 11:32:43 -08:00
Greg Johnston
4648fb2cfc Update README.md 2024-01-25 20:34:15 -05:00
Saber Haj Rabiee
817ec045f7 chore: fix import of quote::quote (#2227) 2024-01-25 17:48:41 -05:00
Greg Johnston
ca3806e6bc v0.6.0-rc1 2024-01-24 21:35:14 -05:00
Greg Johnston
936c2077c3 Merge pull request #2222 from leptos-rs/2221
fix: `.refetch()` should not include any tracked reads
2024-01-24 20:44:51 -05:00
Greg Johnston
b3b18875c6 chore: allow unknown lints 2024-01-24 19:43:28 -05:00
Greg Johnston
5cbab48713 chore: avoid possible false positive in cargo check 2024-01-24 19:38:16 -05:00
Greg Johnston
5a8880dd2e fix: correctly track in the effect that creates the resource 2024-01-24 16:18:45 -05:00
Greg Johnston
ea6c957f3d fix: .refetch() should not track any reads (closes #2221) 2024-01-23 11:08:50 -05:00
Greg Johnston
694e5f1cb3 fix: cast to correct type on Memo::try_with_untracked 2024-01-23 11:08:33 -05:00
Greg Johnston
fce2c727ab feat: add support for custom encodings to #[server] macro (#2216) (closes #2210) 2024-01-21 16:14:02 -05:00
Greg Johnston
7d1ce45a57 chore: minimize features activated with leptos_axum's default feature (#1846) (#2213)
- `leptos_axum` default feature:
  - remove `tokio/full`, `axum/macros`
  - add `tokio/fs`, `tokio/sync`
- example `leptos-tailwind-axum`:
  - enable `tokio`'s `rt-multi-thread` and `macros` features
- example `ssr_modes_axum`:
  - enable `tokio`'s `rt-multi-thread` and `macros` features

Co-authored-by: Paul Nettleton <paulnett7@hotmail.com>
2024-01-21 15:22:46 -05:00
Niklas Eicker
997b99081b change: for static routes, remove .static and provide additional context for static_params closures (#2207) 2024-01-21 13:33:05 -05:00
Chris
d33e57d4b7 feat: Default for LeptosOptions, ConfFile (#2208)
Co-authored-by: chrisp60 <gh@cperry.me>
2024-01-21 13:26:10 -05:00
Greg Johnston
b450f0fd10 fix: don't enable tracing feature on leptos by default (#2211) 2024-01-20 17:58:22 -05:00
Greg Johnston
c84c6ee8cd Merge pull request #2158 from leptos-rs/leptos_v0.6 2024-01-20 15:58:25 -05:00
Greg Johnston
567644df8f clarify docs here 2024-01-20 14:29:22 -05:00
Greg Johnston
39f5481b8c clean up in docs and rename Axum extract() to match Actix extract() 2024-01-20 14:29:08 -05:00
Greg Johnston
c88bfbe0a0 tweak sets of features for CI 2024-01-20 14:18:25 -05:00
Greg Johnston
40da1fe94e clippy 2024-01-20 14:16:13 -05:00
Greg Johnston
8df46fcdb9 examples: use old Axum version of hackernews_js_fetch until supported by axum-js-fetch 2024-01-20 12:39:16 -05:00
Greg Johnston
b4a1d90327 clean up for CI 2024-01-20 12:32:51 -05:00
Chris
d746f83387 docs: View::render_to_string panic (#2200)
Co-authored-by: chrisp60 <gh@cperry.me>
2024-01-19 17:07:39 -08:00
Greg Johnston
2092c40bc7 missing derives 2024-01-19 18:21:57 -05:00
Greg Johnston
70ec0c2d0a update sso example 2024-01-19 18:02:22 -05:00
Greg Johnston
eb45d05f3b clippy 2024-01-19 17:43:05 -05:00
Greg Johnston
f19def9541 clippy 2024-01-19 16:55:16 -05:00
Greg Johnston
ddda785045 fix multipart support 2024-01-19 16:52:41 -05:00
Greg Johnston
26d9d75cf2 cargo fmt 2024-01-19 15:56:57 -05:00
Greg Johnston
46e7abf9ba allow custom req/res/client types 2024-01-19 15:48:14 -05:00
Greg Johnston
1b1e02729e clean up examples 2024-01-19 15:17:17 -05:00
Greg Johnston
fdd576535a clean up examples 2024-01-19 15:14:39 -05:00
Greg Johnston
2a9e502893 fix rkyv deserialization 2024-01-19 15:03:21 -05:00
Greg Johnston
a519859a66 Revert "use &[u8] instead of Bytes for requests"
This reverts commit e179db1d42.
2024-01-19 14:37:03 -05:00
Greg Johnston
25120c0e9f fix streaming requests and clarify in docs 2024-01-19 14:17:26 -05:00
Greg Johnston
94cb4c0ec3 remove pavex work (now in pavex branch) 2024-01-19 14:17:26 -05:00
Greg Johnston
f9cd8539e4 add missing PartialEq/Eq implementations on ServerFnError (closes #2198) 2024-01-19 14:17:26 -05:00
Greg Johnston
14072457d0 clean up docs (closes #2197) 2024-01-19 14:17:26 -05:00
Greg Johnston
e179db1d42 use &[u8] instead of Bytes for requests 2024-01-19 14:17:26 -05:00
Greg Johnston
2fa60103b4 share inventory collect across types 2024-01-19 14:17:26 -05:00
Greg Johnston
a3a15f244d expose all fields of ServerFnTraitObj via methods 2024-01-19 14:17:26 -05:00
Greg Johnston
0df5dfeaf8 weak dependency on Cargo.toml 2024-01-19 14:17:26 -05:00
Greg Johnston
3f22906053 fix warning 2024-01-19 14:17:26 -05:00
Greg Johnston
33ad30515d serde-lite support should be enabled directly on server_fn 2024-01-19 14:17:26 -05:00
Greg Johnston
c5bab09423 partial support for streaming requests (doesn't actually work in the browser) 2024-01-19 14:17:26 -05:00
Greg Johnston
320179bc04 remove misleading warning 2024-01-19 14:17:26 -05:00
Greg Johnston
5065bed594 example of middleware that can run before and/or after server fn 2024-01-19 14:17:26 -05:00
Greg Johnston
22b4537f27 fix version numbers 2024-01-19 14:17:26 -05:00
Greg Johnston
8d23d5136a add package metadata 2024-01-19 14:17:25 -05:00
Greg Johnston
c7fac64054 fix merge error 2024-01-19 14:17:25 -05:00
Greg Johnston
047235e7c1 clippy 2024-01-19 14:17:25 -05:00
Greg Johnston
7a086ad159 update version number 2024-01-19 14:17:25 -05:00
Greg Johnston
bb923b3f9b erroneous hyphen 2024-01-19 14:16:59 -05:00
Greg Johnston
6a8c26a820 streaming example with filesystem watcher 2024-01-19 14:16:59 -05:00
Greg Johnston
21f8085851 add streaming/file watcher example 2024-01-19 14:16:59 -05:00
Greg Johnston
9a5a102ce3 add middleware to kitchen-sink example 2024-01-19 14:16:59 -05:00
Greg Johnston
4d602c21f8 example with custom errors 2024-01-19 14:16:59 -05:00
Greg Johnston
7d114c7414 file upload example 2024-01-19 14:16:58 -05:00
Greg Johnston
1f017a2ade hm custom encodings have orphan rule issues 2024-01-19 14:16:58 -05:00
Greg Johnston
35e8e74dcf get rkyv working and work on custom encoding example 2024-01-19 14:16:58 -05:00
Markus Kohlhase
4366d786ac Update login example (CSR only) (#2155) 2024-01-19 14:16:58 -05:00
Ari Seyhun
1777a4057a fix!: remove clone in Cow<'static, str> IntoView impl (#1946) 2024-01-19 14:16:58 -05:00
Greg Johnston
0571ebbc36 working on example 2024-01-19 14:16:58 -05:00
Greg Johnston
06c478b7cb feature-gate the form redirect stuff, and clear old errors from query 2024-01-19 14:16:58 -05:00
Greg Johnston
90ba3529e9 working on Axum version 2024-01-19 14:16:58 -05:00
Greg Johnston
13a2691806 working on server fn example 2024-01-19 14:16:58 -05:00
Greg Johnston
1ad7ee8a03 generalize error redirect behavior across integrations 2024-01-19 14:16:58 -05:00
Greg Johnston
88fee243a8 support setting server URL on either platform 2024-01-19 14:16:58 -05:00
Greg Johnston
5e08253521 get both client and server side working 2024-01-19 14:16:58 -05:00
Greg Johnston
cc6f65cd83 initial version of server action error handling without JS 2024-01-19 14:16:58 -05:00
Greg Johnston
9488114801 docs 2024-01-19 14:16:58 -05:00
Greg Johnston
b0cdeab906 remove old code 2024-01-19 14:16:58 -05:00
Greg Johnston
def4be80b2 docs 2024-01-19 14:16:58 -05:00
Greg Johnston
15b04a8a85 more docs 2024-01-19 14:16:58 -05:00
Greg Johnston
0a9cdba22e getting started on docs 2024-01-19 14:16:58 -05:00
Greg Johnston
1d1de4ac38 remove cfg-if from all examples 2024-01-19 14:16:58 -05:00
Greg Johnston
31b2b9e94c remove explicit handle_server_fns in most cases because it's now included in .leptos_routes() 2024-01-19 14:16:58 -05:00
Greg Johnston
8f07818687 nicer formatting, remove cfg-if 2024-01-19 14:16:58 -05:00
Greg Johnston
a5cbfa0aad remove viz integration (see #2177) 2024-01-19 14:16:58 -05:00
Greg Johnston
6c8e704fb3 smh 2024-01-19 14:16:58 -05:00
Greg Johnston
81fb5160e5 missing makefiles 2024-01-19 14:16:58 -05:00
Greg Johnston
2af0d3d781 update session_auth_axum 2024-01-19 14:16:58 -05:00
Greg Johnston
7f532cda70 update todo_app_sqlite_csrs 2024-01-19 14:16:58 -05:00
Greg Johnston
c7941f7639 clippy 2024-01-19 14:16:58 -05:00
Greg Johnston
61148026d1 allow type paths for input/output, and properly namespace built-in encodings 2024-01-19 14:16:58 -05:00
Greg Johnston
738eeefe73 chore: clear warnings 2024-01-19 14:16:18 -05:00
Greg Johnston
be084a5d1d remove list of magic identifiers, use rust-analyzer to help with imports instead 2024-01-19 14:16:18 -05:00
Greg Johnston
f5c007df7b use server fns directly in ActionForm and MultiActionForm 2024-01-19 14:16:18 -05:00
Rakshith Ravi
a1bd84f3dc feat: add serde-lite codec for server functions (#2168) 2024-01-19 14:16:18 -05:00
Rakshith Ravi
f6ce82c9d1 Fixed tests for server_fn (#2167)
* Fixed server_fn tests

* Changed type_name to TypeId

* Fixed handling of leading slashes for server_fn endpoint
2024-01-19 14:16:18 -05:00
Greg Johnston
853c080707 add missing server fn registration 2024-01-19 14:16:18 -05:00
Greg Johnston
f6b95e40f4 make sure endpoint names begin with a / 2024-01-19 14:16:18 -05:00
Greg Johnston
db1497b9c2 set version, input, etc. correctly 2024-01-19 14:16:18 -05:00
Greg Johnston
f53ac1a4ae remove unused var 2024-01-19 14:16:18 -05:00
Greg Johnston
5e6f4403ca set up redirects in Actix 2024-01-19 14:16:18 -05:00
Greg Johnston
4e3f1c834c handle client-side and server-side redirects correctly (in Axum) 2024-01-19 14:16:18 -05:00
Greg Johnston
566df034ff actually use server functions in ActionForm 2024-01-19 14:16:17 -05:00
Greg Johnston
fd97e2e027 Restore the previous full functionality of Form 2024-01-19 14:16:17 -05:00
Greg Johnston
c8fbee18c8 finished Actix support? 2024-01-19 14:16:17 -05:00
Greg Johnston
e1a9856ca9 more Actix work 2024-01-19 14:16:17 -05:00
Greg Johnston
60efaefff4 start Actix work 2024-01-19 14:16:17 -05:00
Greg Johnston
db4158f5c3 clear up warnings 2024-01-19 14:16:17 -05:00
Greg Johnston
af62d2e900 automatically include server function handler in .leptos_router() 2024-01-19 14:16:17 -05:00
Greg Johnston
c3e3ce7878 changes to get todo_app_sqlite_axum example working 2024-01-19 14:16:17 -05:00
Greg Johnston
dec17fc65b fix server actions and server multi actions 2024-01-19 14:16:03 -05:00
Greg Johnston
2dbc5899f3 cargo fmt 2024-01-19 14:16:03 -05:00
Greg Johnston
dd368a845c @ealmloff changes to reexport actix/axum 2024-01-19 14:16:03 -05:00
Greg Johnston
9c258219dd fix Actix implementation with middleware 2024-01-19 14:16:03 -05:00
Greg Johnston
6a1685936b fix rkyv 2024-01-19 14:16:03 -05:00
Greg Johnston
7d45e6bb13 clean up my mistake 2024-01-19 14:16:03 -05:00
Greg Johnston
8fae76828e FromStr-based lightweight ServerFnError deserialization 2024-01-19 14:16:03 -05:00
Greg Johnston
d5b9e84f36 properly gate inventory 2024-01-19 14:16:03 -05:00
benwis
197edebd51 Made some progress, started work on pavex integration as well 2024-01-19 14:16:03 -05:00
benwis
2a5c855595 It starts to compile! 2024-01-19 14:16:03 -05:00
benwis
c9627bfeb4 Setup folder structure as before. Got a cyclical dependency though 2024-01-19 14:16:03 -05:00
benwis
c7422cd96e First commit, checkpoint for cyclical dependency error 2024-01-19 14:15:51 -05:00
Daniel Santana
cadd217078 Update integration with support for axum 0.7 (#2082)
* chore: update to axum 0.7

Removed http, since it's included in axum, and replaced hyper by http-body-util, which is a smaller.

* chore: update samples to work with nre axum

Missing sessions_axum_auth, pending PR merge.

* chore: all dependencies update to axum 0.7

* chore: cargo fmt

* chore: fix doctests

* chore: Fix example that in reality doesn't use axum.

Fixed anyway.

* chore: more examples support for axum 0.7

* Small tweak
2024-01-19 14:13:55 -05:00
Greg Johnston
0c4cf5471d v0.5.7 2024-01-19 13:03:44 -05:00
Greg Johnston
dd0c349554 examples: update axum-session because old version was yanked (#2205) 2024-01-19 12:54:08 -05:00
269 changed files with 4008 additions and 6273 deletions

View File

@@ -29,4 +29,4 @@ jobs:
with:
directory: ${{ matrix.directory }}
cargo_make_task: "ci"
toolchain: nightly
toolchain: nightly-2024-03-31

View File

@@ -24,4 +24,4 @@ jobs:
with:
directory: ${{ matrix.directory }}
cargo_make_task: "ci"
toolchain: nightly
toolchain: nightly-2024-03-31

22
.github/workflows/ci-semver.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: CI semver
on: workflow_dispatch # Only run this workflow manually
jobs:
get-leptos-changed:
uses: ./.github/workflows/get-leptos-changed.yml
test:
needs: [get-leptos-changed]
if: needs.get-leptos-changed.outputs.leptos_changed == 'true'
name: Run semver check (nightly-2024-03-31)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Sember Checks
uses: obi1kenobi/cargo-semver-checks-action@v2
with:
rust-toolchain: nightly-2024-03-31

View File

@@ -40,4 +40,4 @@ jobs:
with:
directory: ${{ matrix.directory }}
cargo_make_task: "ci"
toolchain: nightly
toolchain: nightly-2024-03-31

View File

@@ -31,10 +31,9 @@ jobs:
dir_names: true
dir_names_max_depth: "2"
files: |
examples
!examples/cargo-make
!examples/gtk
!examples/hackernews_js_fetch
examples/**
!examples/cargo-make/**
!examples/gtk/**
!examples/Makefile.toml
!examples/*.md
json: true

View File

@@ -21,12 +21,12 @@ jobs:
- name: Get example files that changed
id: changed-files
uses: tj-actions/changed-files@v41
uses: tj-actions/changed-files@v43
with:
files: |
examples/**
!examples/cargo-make
!examples/gtk
!examples/cargo-make/**
!examples/gtk/**
!examples/Makefile.toml
!examples/*.md

View File

@@ -17,8 +17,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install JQ Tool
uses: mbround18/install-jq@v1
- name: Install jq
run: sudo apt-get install jq
- name: Set Matrix
id: set-matrix

View File

@@ -19,7 +19,7 @@ jobs:
- name: Get source files that changed
id: changed-source
uses: tj-actions/changed-files@v41
uses: tj-actions/changed-files@v43
with:
files: |
integrations/**

View File

@@ -27,11 +27,9 @@ jobs:
- uses: actions/checkout@v4
- name: Setup Rust
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ inputs.toolchain }}
override: true
components: rustfmt
- name: Add wasm32-unknown-unknown
run: rustup target add wasm32-unknown-unknown
@@ -44,6 +42,18 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Install binstall
uses: cargo-bins/cargo-binstall@main
- name: Install wasm-bindgen
run: cargo binstall wasm-bindgen-cli --no-confirm
- name: Install wasm-pack
run: cargo binstall wasm-pack --no-confirm
- name: Install cargo-leptos
run: cargo binstall cargo-leptos --no-confirm
- name: Install Trunk
uses: jetli/trunk-action@v0.4.0
with:
@@ -55,9 +65,9 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v3
name: Install pnpm
id: pnpm-install
with:
@@ -69,7 +79,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
@@ -107,6 +117,11 @@ jobs:
fi
done
- name: Install Deno
uses: denoland/setup-deno@v1
with:
deno-version: v1.x
# Run Cargo Make Task
- name: ${{ inputs.cargo_make_task }}
run: |

View File

@@ -25,22 +25,23 @@ members = [
exclude = ["benchmarks", "examples"]
[workspace.package]
version = "0.6.0-beta"
version = "0.6.9"
rust-version = "1.75"
[workspace.dependencies]
leptos = { path = "./leptos", version = "0.6.0-beta" }
leptos_dom = { path = "./leptos_dom", version = "0.6.0-beta" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.0-beta" }
leptos_macro = { path = "./leptos_macro", version = "0.6.0-beta" }
leptos_reactive = { path = "./leptos_reactive", version = "0.6.0-beta" }
leptos_server = { path = "./leptos_server", version = "0.6.0-beta" }
server_fn = { path = "./server_fn", version = "0.6.0-beta" }
server_fn_macro = { path = "./server_fn_macro", version = "0.6.0-beta" }
leptos = { path = "./leptos", version = "0.6.9" }
leptos_dom = { path = "./leptos_dom", version = "0.6.9" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.9" }
leptos_macro = { path = "./leptos_macro", version = "0.6.9" }
leptos_reactive = { path = "./leptos_reactive", version = "0.6.9" }
leptos_server = { path = "./leptos_server", version = "0.6.9" }
server_fn = { path = "./server_fn", version = "0.6.9" }
server_fn_macro = { path = "./server_fn_macro", version = "0.6.9" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.6" }
leptos_config = { path = "./leptos_config", version = "0.6.0-beta" }
leptos_router = { path = "./router", version = "0.6.0-beta" }
leptos_meta = { path = "./meta", version = "0.6.0-beta" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.0-beta" }
leptos_config = { path = "./leptos_config", version = "0.6.9" }
leptos_router = { path = "./router", version = "0.6.9" }
leptos_meta = { path = "./meta", version = "0.6.9" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.9" }
[profile.release]
codegen-units = 1

View File

@@ -40,6 +40,25 @@ pub fn SimpleCounter(initial_value: i32) -> impl IntoView {
}
}
// we also support a builder syntax rather than the JSX-like `view` macro
#[component]
pub fn SimpleCounterWithBuilder(initial_value: i32) -> impl IntoView {
use leptos::html::*;
let (value, set_value) = create_signal(initial_value);
let clear = move |_| set_value(0);
let decrement = move |_| set_value.update(|value| *value -= 1);
let increment = move |_| set_value.update(|value| *value += 1);
// the `view` macro above expands to this builder syntax
div().child((
button().on(ev::click, clear).child("Clear"),
button().on(ev::click, decrement).child("-1"),
span().child(("Value: ", value, "!")),
button().on(ev::click, increment).child("+1")
))
}
// Easy to use with Trunk (trunkrs.dev) or with a simple wasm-bindgen setup
pub fn main() {
mount_to_body(|| view! {
@@ -131,7 +150,7 @@ There are several people in the community using Leptos right now for internal ap
### Can I use this for native GUI?
Sure! Obviously the `view` macro is for generating DOM nodes but you can use the reactive system to drive native any GUI toolkit that uses the same kind of object-oriented, event-callback-based framework as the DOM pretty easily. The principles are the same:
Sure! Obviously the `view` macro is for generating DOM nodes but you can use the reactive system to drive any native GUI toolkit that uses the same kind of object-oriented, event-callback-based framework as the DOM pretty easily. The principles are the same:
- Use signals, derived signals, and memos to create your reactive system
- Create GUI widgets
@@ -140,35 +159,27 @@ Sure! Obviously the `view` macro is for generating DOM nodes but you can use the
I've put together a [very simple GTK example](https://github.com/leptos-rs/leptos/blob/main/examples/gtk/src/main.rs) so you can see what I mean.
### How is this different from Yew/Dioxus?
The new rendering approach being developed for 0.7 supports “universal rendering,” i.e., it can use any rendering library that supports a small set of 6-8 functions. (This is intended as a layer over typical retained-mode, OOP-style GUI toolkits like the DOM, GTK, etc.) That future rendering work will allow creating native UI in a way that is much more similar to the declarative approach used by the web framework.
On the surface level, these libraries may seem similar. Yew is, of course, the most mature Rust library for web UI development and has a huge ecosystem. Dioxus is similar in many ways, being heavily inspired by React. Here are some conceptual differences between Leptos and these frameworks:
### How is this different from Yew?
Yew is the most-used library for Rust web UI development, but there are several differences between Yew and Leptos, in philosophy, approach, and performance.
- **VDOM vs. fine-grained:** Yew is built on the virtual DOM (VDOM) model: state changes cause components to re-render, generating a new virtual DOM tree. Yew diffs this against the previous VDOM, and applies those patches to the actual DOM. Component functions rerun whenever state changes. Leptos takes an entirely different approach. Components run once, creating (and returning) actual DOM nodes and setting up a reactive system to update those DOM nodes.
- **Performance:** This has huge performance implications: Leptos is simply much faster at both creating and updating the UI than Yew is. (Dioxus has made huge advances in performance with its recent 0.3 release, and is now roughly on par with Leptos.)
- **Mental model:** Adopting fine-grained reactivity also tends to simplify the mental model. There are no surprising component re-renders because there are no re-renders. You can call functions, create timeouts, etc. within the body of your component functions because they wont be re-run. You dont need to think about manual dependency tracking for effects; fine-grained reactivity tracks dependencies automatically.
- **Performance:** This has huge performance implications: Leptos is simply much faster at both creating and updating the UI than Yew is.
- **Server integration:** Yew was created in an era in which browser-rendered single-page apps (SPAs) were the dominant paradigm. While Leptos supports client-side rendering, it also focuses on integrating with the server side of your application via server functions and multiple modes of serving HTML, including out-of-order streaming.
### How is this different from Sycamore?
- ### How is this different from Dioxus?
Conceptually, these two frameworks are very similar: because both are built on fine-grained reactivity, most apps will end up looking very similar between the two, and Sycamore or Leptos apps will both look a lot like SolidJS apps, in the same way that Yew or Dioxus can look a lot like React.
Like Leptos, Dioxus is a framework for building UIs using web technologies. However, there are significant differences in approach and features.
There are some practical differences that make a significant difference:
- **VDOM vs. fine-grained:** While Dioxus has a performant virtual DOM (VDOM), it still uses coarse-grained/component-scoped reactivity: changing a stateful value reruns the component function and diffs the old UI against the new one. Leptos components use a different mental model, creating (and returning) actual DOM nodes and setting up a reactive system to update those DOM nodes.
- **Web vs. desktop priorities:** Dioxus uses Leptos server functions in its fullstack mode, but does not have the same `<Suspense>`-based support for things like streaming HTML rendering, or share the same focus on holistic web performance. Leptos tends to prioritize holistic web performance (streaming HTML rendering, smaller WASM binary sizes, etc.), whereas Dioxus has an unparalleled experience when building desktop apps, because your application logic runs as a native Rust binary.
- **Templating:** Leptos uses a JSX-like template format (built on [syn-rsx](https://github.com/stoically/syn-rsx)) for its `view` macro. Sycamore offers the choice of its own templating DSL or a builder syntax.
- **Server integration:** Leptos provides primitives that encourage HTML streaming and allow for easy async integration and RPC calls, even without WASM enabled, making it easy to opt into integrations between your frontend and backend code without pushing you toward any particular metaframework patterns.
- **Read-write segregation:** Leptos, like Solid, encourages read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(0);` _(If you prefer or if it's more convenient for your API, you can use [`create_rw_signal`](https://docs.rs/leptos/latest/leptos/fn.create_rw_signal.html) to give a unified read/write signal.)_
- **Signals are functions:** In Leptos, you can call a signal to access it rather than calling a specific method (so, `count()` instead of `count.get()`) This creates a more consistent mental model: accessing a reactive value is always a matter of calling a function. For example:
- ### How is this different from Sycamore?
```rust
let (count, set_count) = create_signal(0); // a signal
let double_count = move || count() * 2; // a derived signal
let memoized_count = create_memo(move |_| count() * 3); // a memo
// all are accessed by calling them
assert_eq!(count(), 0);
assert_eq!(double_count(), 0);
assert_eq!(memoized_count(), 0);
// this function can accept any of those signals
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
```
Sycamore and Leptos are both heavily influenced by SolidJS. At this point, Leptos has a larger community and ecosystem and is more actively developed. Other differences:
- **Signals and scopes are `'static`:** Both Leptos and Sycamore ease the pain of moving signals in closures (in particular, event listeners) by making them `Copy`, to avoid the `{ let count = count.clone(); move |_| ... }` that's very familiar in Rust UI code. Sycamore does this by using bump allocation to tie the lifetimes of its signals to its scopes: since references are `Copy`, `&'a Signal<T>` can be moved into a closure. Leptos does this by using arena allocation and passing around indices: types like `ReadSignal<T>`, `WriteSignal<T>`, and `Memo<T>` are actually wrappers for indices into an arena. This means that both scopes and signals are both `Copy` and `'static` in Leptos, which means that they can be moved easily into closures without adding lifetime complexity.
- **Templating DSLs:** Sycamore uses a custom templating language for its views, while Leptos uses a JSX-like template format.
- **`'static` signals:** One of Leptoss main innovations was the creation of `Copy + 'static` signals, which have excellent ergonomics. Sycamore is in the process of adopting the same pattern, but this is not yet released.
- **Perseus vs. server functions:** The Perseus metaframework provides an opinionated way to build Sycamore apps that include server functionality. Leptos instead provides primitives like server functions in the core of the framework.

View File

@@ -2,6 +2,7 @@
name = "benchmarks"
version = "0.1.0"
edition = "2021"
rust-version.workspace = true
[dependencies]
l0410 = { package = "leptos", version = "0.4.10", features = [

View File

@@ -3,5 +3,5 @@ alias = "check-all"
[tasks.check-all]
command = "cargo"
args = ["+nightly", "check-all-features"]
args = ["+nightly-2024-03-31", "check-all-features"]
install_crate = "cargo-all-features"

View File

@@ -8,4 +8,11 @@ args = ["fmt", "--", "--check", "--config-path", "${LEPTOS_PROJECT_DIRECTORY}"]
[tasks.clippy-each-feature]
dependencies = ["install-clippy"]
command = "cargo"
args = ["hack", "clippy", "--all", "--each-feature", "--no-dev-deps"]
args = [
"clippy",
"--all-features",
"--no-deps",
"--",
"-D",
"clippy::print_stdout",
]

View File

@@ -3,5 +3,5 @@ alias = "test-all"
[tasks.test-all]
command = "cargo"
args = ["+nightly", "test-all-features"]
args = ["+nightly-2024-03-31", "test-all-features"]
install_crate = "cargo-all-features"

View File

@@ -5,6 +5,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CARGO_BUILD_TEST_FLAGS = ""
CARGO_MAKE_WORKSPACE_EMULATION = true
CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = [
"action-form-error-handling",
"animated_show",
"counter",
"counter_isomorphic",
@@ -12,27 +13,33 @@ CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = [
"counters_stable",
"counter_url_query",
"counter_without_macros",
"directives",
"error_boundary",
"errors_axum",
"fetch",
"hackernews",
"hackernews_axum",
"hackernews_islands_axum",
"hackernews_js_fetch",
"js-framework-benchmark",
"login_with_token_csr_only",
"parent_child",
"portal",
"router",
"server_fns_axum",
"session_auth_axum",
"slots",
"sso_auth_axum",
"ssr_modes",
"ssr_modes_axum",
"suspense_tests",
"tailwind_actix",
"tailwind_csr",
"tailwind_axum",
"tailwind_csr",
"timer",
"todo_app_sqlite",
"todo_app_sqlite_axum",
"todo_app_sqlite_csr",
"todomvc",
]
@@ -51,103 +58,5 @@ echo "CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = $examples"
[tasks.test-report]
workspace = false
description = "report web testing technology used by examples - OPTION: [all]"
script = '''
set -emu
BOLD="\e[1m"
GREEN="\e[0;32m"
ITALIC="\e[3m"
YELLOW="\e[0;33m"
RESET="\e[0m"
echo
echo "${YELLOW}Web Test Technology${RESET}"
echo
makefile_paths=$(find . -name Makefile.toml -not -path '*/target/*' -not -path '*/node_modules/*' |
sed 's%./%%' |
sed 's%/Makefile.toml%%' |
grep -v Makefile.toml |
sort -u)
start_path=$(pwd)
for path in $makefile_paths; do
cd $path
crate_symbols=
pw_count=$(find . -name playwright.config.ts | wc -l)
while read -r line; do
case $line in
*"cucumber"*)
crate_symbols=$crate_symbols"C"
;;
*"fantoccini"*)
crate_symbols=$crate_symbols"D"
;;
esac
done <"./Cargo.toml"
while read -r line; do
case $line in
*"cargo-make/wasm-test.toml"*)
crate_symbols=$crate_symbols"W"
;;
*"cargo-make/playwright-test.toml"*)
crate_symbols=$crate_symbols"P"
crate_symbols=$crate_symbols"N"
;;
*"cargo-make/playwright-trunk-test.toml"*)
crate_symbols=$crate_symbols"P"
crate_symbols=$crate_symbols"T"
;;
*"cargo-make/trunk_server.toml"*)
crate_symbols=$crate_symbols"T"
;;
*"cargo-make/cargo-leptos-webdriver-test.toml"*)
crate_symbols=$crate_symbols"L"
;;
*"cargo-make/cargo-leptos-test.toml"*)
crate_symbols=$crate_symbols"L"
if [ $pw_count -gt 0 ]; then
crate_symbols=$crate_symbols"P"
fi
;;
esac
done <"./Makefile.toml"
# Sort list of tools
sorted_crate_symbols=$(echo ${crate_symbols} | grep -o . | sort | tr -d "\n")
formatted_crate_symbols="${BOLD}${YELLOW}${sorted_crate_symbols}${RESET}"
crate_line=$path
if [ ! -z ${1+x} ]; then
# Show all examples
if [ ! -z $crate_symbols ]; then
crate_line=$crate_line$formatted_crate_symbols
fi
echo $crate_line
elif [ ! -z $crate_symbols ]; then
# Filter out examples that do not run tests in `ci`
crate_line=$crate_line$formatted_crate_symbols
echo $crate_line
fi
cd ${start_path}
done
c="${BOLD}${YELLOW}C${RESET} = Cucumber"
d="${BOLD}${YELLOW}D${RESET} = WebDriver"
l="${BOLD}${YELLOW}L${RESET} = Cargo Leptos"
n="${BOLD}${YELLOW}N${RESET} = Node"
p="${BOLD}${YELLOW}P${RESET} = Playwright"
t="${BOLD}${YELLOW}T${RESET} = Trunk"
w="${BOLD}${YELLOW}W${RESET} = WASM"
echo
echo "${ITALIC}Keys:${RESET} $c, $d, $l, $n, $p, $t, $w"
echo
'''
description = "show the cargo-make configuration for web examples [web|all|help]"
script = { file = "./cargo-make/scripts/web-report.sh" }

View File

@@ -16,7 +16,7 @@ You can also run any of the examples using [`cargo-make`](https://github.com/sag
Follow these steps to get any example up and running.
1. `cd` to the example root directory
1. `cd` to the example you want to run
2. Run `cargo make ci` to setup and test the example
3. Run `cargo make start` to run the example
4. Open the client URL in the console output (<http://127.0.0.1:8080> or <http://127.0.0.1:3000> by default)

View File

@@ -28,7 +28,9 @@ pub fn App() -> impl IntoView {
}
#[server]
async fn do_something(should_error: Option<String>) -> Result<String, ServerFnError> {
async fn do_something(
should_error: Option<String>,
) -> Result<String, ServerFnError> {
if should_error.is_none() {
Ok(String::from("Successful submit"))
} else {
@@ -42,7 +44,12 @@ async fn do_something(should_error: Option<String>) -> Result<String, ServerFnEr
#[component]
fn HomePage() -> impl IntoView {
let do_something_action = Action::<DoSomething, _>::server();
let value = Signal::derive(move || do_something_action.value().get().unwrap_or_else(|| Ok(String::new())));
let value = Signal::derive(move || {
do_something_action
.value()
.get()
.unwrap_or_else(|| Ok(String::new()))
});
Effect::new_isomorphic(move |_| {
logging::log!("Got value = {:?}", value.get());

View File

@@ -1,11 +1,11 @@
#[cfg(feature = "ssr")]
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use action_form_error_handling::app::*;
use actix_files::Files;
use actix_web::*;
use leptos::*;
use leptos_actix::{generate_route_list, LeptosRoutes};
use action_form_error_handling::app::*;
let conf = get_configuration(None).await.unwrap();
let addr = conf.leptos_options.site_addr;
@@ -43,8 +43,8 @@ pub fn main() {
// a client-side main function is required for using `trunk serve`
// prefer using `cargo leptos serve` instead
// to run: `trunk serve --open --features csr`
use leptos::*;
use action_form_error_handling::app::*;
use leptos::*;
use wasm_bindgen::prelude::wasm_bindgen;
console_error_panic_hook::set_once();

View File

@@ -15,13 +15,13 @@ clear = true
dependencies = ["check-debug", "check-release"]
[tasks.check-debug]
toolchain = "nightly"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features"]
install_crate = "cargo-all-features"
[tasks.check-release]
toolchain = "nightly"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features", "--release"]
install_crate = "cargo-all-features"

View File

@@ -4,11 +4,13 @@ dependencies = [
"clean-trunk",
"clean-node_modules",
"clean-playwright",
"clean-pkg",
]
[tasks.clean-cargo]
command = "rm"
args = ["-rf", "target"]
script = '''
find . -type d -name target | xargs rm -rf
'''
[tasks.clean-trunk]
script = '''
@@ -27,3 +29,8 @@ fi
script = '''
find . -name playwright-report -name playwright -name test-results | xargs rm -rf
'''
[tasks.clean-pkg]
script = '''
find . -type d -name pkg | xargs rm -rf
'''

View File

@@ -3,32 +3,36 @@
[tasks.stop-client]
condition = { env_set = ["CLIENT_PROCESS_NAME"] }
script = '''
if [ ! -z $(pidof ${CLIENT_PROCESS_NAME}) ]; then
if pidof -q ${CLIENT_PROCESS_NAME}; then
echo " Stopping ${CLIENT_PROCESS_NAME}"
pkill -ef ${CLIENT_PROCESS_NAME}
else
echo " ${CLIENT_PROCESS_NAME} is already stopped"
fi
'''
[tasks.client-status]
condition = { env_set = ["CLIENT_PROCESS_NAME"] }
script = '''
if [ -z $(pidof ${CLIENT_PROCESS_NAME}) ]; then
echo " ${CLIENT_PROCESS_NAME} is not running"
else
if pidof -q ${CLIENT_PROCESS_NAME}; then
echo " ${CLIENT_PROCESS_NAME} is up"
else
echo " ${CLIENT_PROCESS_NAME} is not running"
fi
'''
[tasks.maybe-start-client]
condition = { env_set = ["CLIENT_PROCESS_NAME"] }
script = '''
if [ -z $(pidof ${CLIENT_PROCESS_NAME}) ]; then
if pidof -q ${CLIENT_PROCESS_NAME}; then
echo " ${CLIENT_PROCESS_NAME} is already started"
else
echo " Starting ${CLIENT_PROCESS_NAME}"
if [ -z ${SPAWN_CLIENT_PROCESS} ];then
if [ -n "${SPAWN_CLIENT_PROCESS}" ];then
echo "Spawning process..."
cargo make start-client ${@} &
else
cargo make start-client ${@}
fi
else
echo " ${CLIENT_PROCESS_NAME} is already started"
fi
'''

View File

@@ -1,11 +1,11 @@
[tasks.build]
toolchain = "nightly"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["build-all-features"]
install_crate = "cargo-all-features"
[tasks.check]
toolchain = "nightly"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features"]
install_crate = "cargo-all-features"

View File

@@ -0,0 +1,24 @@
[tasks.build]
clear = true
command = "deno"
args = ["task", "build"]
[tasks.start-client]
command = "deno"
args = ["task", "start"]
[tasks.check]
clear = true
dependencies = ["check-debug", "check-release"]
[tasks.check-debug]
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features"]
install_crate = "cargo-all-features"
[tasks.check-release]
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features", "--release"]
install_crate = "cargo-all-features"

View File

@@ -1,5 +1,5 @@
[tasks.pre-clippy]
env = { CARGO_MAKE_CLIPPY_ARGS = "--all-targets --all-features -- -D warnings" }
env = { CARGO_MAKE_CLIPPY_ARGS = "--no-deps --all-targets --all-features -- -D warnings" }
[tasks.check-style]
dependencies = ["check-format-flow", "clippy-flow"]

View File

@@ -6,9 +6,17 @@ extend = [
[tasks.integration-test]
description = "Run integration test with automated start and stop of processes"
env = { SPAWN_CLIENT_PROCESS = "1" }
dependencies = ["start", "wait-one", "test-playwright", "stop"]
run_task = { name = ["start", "wait-test-stop"], parallel = true }
[tasks.wait-one]
[tasks.wait-test-stop]
private = true
dependencies = ["wait-server", "test-playwright", "stop"]
[tasks.wait-server]
script = '''
sleep 1
for run in {1..12}; do
echo "Waiting to ensure server is started..."
sleep 10
done
echo "Times up, running tests"
'''

View File

@@ -0,0 +1,176 @@
#!/bin/bash
set -emu
BOLD="\e[1m"
ITALIC="\e[3m"
YELLOW="\e[1;33m"
BLUE="\e[1;36m"
RESET="\e[0m"
function web { #task: only include examples with web cargo-make configuration
print_header
print_crate_tags "$@"
print_footer
}
function all { #task: includes all examples
print_header
print_crate_tags "all"
print_footer
}
function print_header {
echo -e "${YELLOW}Cargo Make Web Report${RESET}"
echo
echo -e "${ITALIC}Show how crates are configured to run and test web examples with cargo-make${RESET}"
echo
}
function print_crate_tags {
local makefile_paths
makefile_paths=$(find_makefile_lines)
local start_path
start_path=$(pwd)
for path in $makefile_paths; do
cd "$path"
local crate_tags=
# Add cargo tags
while read -r line; do
case $line in
*"cucumber"*)
crate_tags=$crate_tags"C"
;;
*"fantoccini"*)
crate_tags=$crate_tags"F"
;;
*"package.metadata.leptos"*)
crate_tags=$crate_tags"M"
;;
esac
done <"./Cargo.toml"
#Add makefile tags
local pw_count
pw_count=$(find . -name playwright.config.ts | wc -l)
while read -r line; do
case $line in
*"cargo-make/wasm-test.toml"*)
crate_tags=$crate_tags"W"
;;
*"cargo-make/playwright-test.toml"*)
crate_tags=$crate_tags"P"
crate_tags=$crate_tags"N"
;;
*"cargo-make/playwright-trunk-test.toml"*)
crate_tags=$crate_tags"P"
crate_tags=$crate_tags"T"
;;
*"cargo-make/trunk_server.toml"*)
crate_tags=$crate_tags"T"
;;
*"cargo-make/cargo-leptos-webdriver-test.toml"*)
crate_tags=$crate_tags"L"
;;
*"cargo-make/cargo-leptos-test.toml"*)
crate_tags=$crate_tags"L"
if [ "$pw_count" -gt 0 ]; then
crate_tags=$crate_tags"P"
fi
;;
*"cargo-make/cargo-leptos.toml"*)
crate_tags=$crate_tags"L"
;;
*"cargo-make/deno-build.toml"*)
crate_tags=$crate_tags"D"
;;
esac
done <"./Makefile.toml"
# Sort tags
local keys
keys=$(echo "$crate_tags" | grep -o . | sort | tr -d "\n")
# Find leptos projects that are not configured to build with cargo-leptos
keys=${keys//"LM"/"L"}
# Find leptos projects that are not configured to build with deno
keys=${keys//"DM"/"D"}
# Maybe print line
local crate_line=$path
if [ -n "$crate_tags" ]; then
local color=$YELLOW
case $keys in
*"M"*)
color=$BLUE
;;
esac
crate_line="$crate_line${color}$keys${RESET}"
echo -e "$crate_line"
elif [ "$#" -gt 0 ]; then
crate_line="${BOLD}$crate_line${RESET}"
echo -e "$crate_line"
fi
cd "$start_path"
done
}
function find_makefile_lines {
find . -name Makefile.toml -not -path '*/target/*' -not -path '*/node_modules/*' |
sed 's%./%%' |
sed 's%/Makefile.toml%%' |
grep -v Makefile.toml |
sort -u
}
function print_footer {
c="${BOLD}${YELLOW}C${RESET} = Cucumber Test Runner"
d="${BOLD}${YELLOW}D${RESET} = Deno"
f="${BOLD}${YELLOW}F${RESET} = Fantoccini WebDriver"
l="${BOLD}${YELLOW}L${RESET} = Cargo Leptos"
m="${BOLD}${BLUE}M${RESET} = Cargo Leptos Metadata Only (${ITALIC}ci is not configured to build with cargo-leptos or deno${RESET})"
n="${BOLD}${YELLOW}N${RESET} = Node"
p="${BOLD}${YELLOW}P${RESET} = Playwright Test"
t="${BOLD}${YELLOW}T${RESET} = Trunk"
w="${BOLD}${YELLOW}W${RESET} = WASM Test"
echo
echo -e "${ITALIC}Report Keys:${RESET}\n $c\n $d\n $f\n $l\n $m\n $n\n $p\n $t\n $w"
echo
}
###################
# HELP
###################
function list_help_for {
local task=$1
grep -E "^function.+ #$task" "$0" |
sed 's/function/ /' |
sed -e "s| { #$task: |~|g" |
column -s"~" -t |
sort
}
function help { #help: show task descriptions
echo -e "${BOLD}Usage:${RESET} ./$(basename "$0") <task> [options]"
echo
echo "Show the cargo-make configuration for web examples"
echo
echo -e "${BOLD}Tasks:${RESET}"
list_help_for task
echo
}
TIMEFORMAT="./web-report.sh completed in %3lR"
time "${@:-all}" # Show the report by default

View File

@@ -3,18 +3,21 @@
[tasks.stop-server]
condition = { env_set = ["SERVER_PROCESS_NAME"] }
script = '''
if [ ! -z $(pidof ${SERVER_PROCESS_NAME}) ]; then
if pidof -q ${SERVER_PROCESS_NAME}; then
echo " Stopping ${SERVER_PROCESS_NAME}"
pkill -ef ${SERVER_PROCESS_NAME}
else
echo " ${SERVER_PROCESS_NAME} is already stopped"
fi
'''
[tasks.server-status]
condition = { env_set = ["SERVER_PROCESS_NAME"] }
script = '''
if [ -z $(pidof ${SERVER_PROCESS_NAME}) ]; then
echo " ${SERVER_PROCESS_NAME} is not running"
else
if pidof -q ${SERVER_PROCESS_NAME}; then
echo " ${SERVER_PROCESS_NAME} is up"
else
echo " ${SERVER_PROCESS_NAME} is not running"
fi
'''
@@ -24,11 +27,11 @@ script = '''
YELLOW="\e[0;33m"
RESET="\e[0m"
if [ -z $(pidof ${SERVER_PROCESS_NAME}) ]; then
if pidof -q ${SERVER_PROCESS_NAME}; then
echo " ${SERVER_PROCESS_NAME} is already started"
else
echo " Starting ${SERVER_PROCESS_NAME}"
echo " ${YELLOW}>> Run cargo make stop to end process${RESET}"
cargo make start-server ${@} &
else
echo " ${SERVER_PROCESS_NAME} is already started"
fi
'''

View File

@@ -6,25 +6,33 @@ script = '''
RESET="\e[0m"
if command -v chromedriver; then
if [ -z $(pidof chromedriver) ]; then
if pidof -q chromedriver; then
echo " chromedriver is already started"
else
echo "Starting chomedriver"
chromedriver --port=4444 &
fi
else
echo "${RED}${BOLD}ERROR${RESET} - chromedriver is required by this task"
echo "${RED}${BOLD}ERROR${RESET} - chromedriver not found"
exit 1
fi
'''
[tasks.stop-webdriver]
script = '''
pkill -f "chromedriver"
if pidof -q chromedriver; then
echo " Stopping chromedriver"
pkill -ef "chromedriver"
else
echo " chromedriver is already stopped"
fi
'''
[tasks.webdriver-status]
script = '''
if [ -z $(pidof chromedriver) ]; then
echo chromedriver is not running
else
if pidof -q chromedriver; then
echo chromedriver is up
else
echo chromedriver is not running
fi
'''

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -25,7 +25,7 @@ leptos_router = { path = "../../router" }
log = "0.4"
once_cell = "1.18"
gloo-net = { git = "https://github.com/rustwasm/gloo" }
wasm-bindgen = "0.2.87"
wasm-bindgen = "0.2"
serde = { version = "1", features = ["derive"] }
simple_logger = "4.3"
tracing = { version = "0.1", optional = true }
@@ -69,7 +69,7 @@ reload-port = 3001
end2end-cmd = "npx playwright test"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"

View File

@@ -5,7 +5,7 @@ use leptos_router::*;
use tracing::instrument;
#[cfg(feature = "ssr")]
mod ssr_imports {
pub mod ssr_imports {
pub use broadcaster::BroadcastChannel;
pub use once_cell::sync::OnceCell;
pub use std::sync::atomic::{AtomicI32, Ordering};
@@ -118,9 +118,9 @@ pub fn Counters() -> impl IntoView {
// This is the typical pattern for a CRUD app
#[component]
pub fn Counter() -> impl IntoView {
let dec = create_action(|_| adjust_server_count(-1, "decing".into()));
let inc = create_action(|_| adjust_server_count(1, "incing".into()));
let clear = create_action(|_| clear_server_count());
let dec = create_action(|_: &()| adjust_server_count(-1, "decing".into()));
let inc = create_action(|_: &()| adjust_server_count(1, "incing".into()));
let clear = create_action(|_: &()| clear_server_count());
let counter = create_resource(
move || {
(
@@ -132,15 +132,6 @@ pub fn Counter() -> impl IntoView {
|_| get_server_count(),
);
let value =
move || counter.get().map(|count| count.unwrap_or(0)).unwrap_or(0);
let error_msg = move || {
counter.get().and_then(|res| match res {
Ok(_) => None,
Err(e) => Some(e),
})
};
view! {
<div>
<h2>"Simple Counter"</h2>
@@ -150,15 +141,24 @@ pub fn Counter() -> impl IntoView {
<div>
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
<button on:click=move |_| dec.dispatch(())>"-1"</button>
<span>"Value: " {value} "!"</span>
<span>
"Value: "
<Suspense>
{move || counter.and_then(|count| *count)} "!"
</Suspense>
</span>
<button on:click=move |_| inc.dispatch(())>"+1"</button>
</div>
{move || {
error_msg()
.map(|msg| {
view! { <p>"Error: " {msg.to_string()}</p> }
})
}}
<Suspense>
{move || {
counter.get().and_then(|res| match res {
Ok(_) => None,
Err(e) => Some(e),
}).map(|msg| {
view! { <p>"Error: " {msg.to_string()}</p> }
})
}}
</Suspense>
</div>
}
}
@@ -204,7 +204,7 @@ pub fn FormCounter() -> impl IntoView {
<input type="hidden" name="msg" value="form value down"/>
<input type="submit" value="-1"/>
</ActionForm>
<span>"Value: " {move || value().to_string()} "!"</span>
<span>"Value: " <Suspense>{move || value().to_string()} "!"</Suspense></span>
<ActionForm action=adjust>
<input type="hidden" name="delta" value="1"/>
<input type="hidden" name="msg" value="form value up"/>
@@ -222,9 +222,10 @@ pub fn FormCounter() -> impl IntoView {
#[component]
pub fn MultiuserCounter() -> impl IntoView {
let dec =
create_action(|_| adjust_server_count(-1, "dec dec goose".into()));
let inc = create_action(|_| adjust_server_count(1, "inc inc moose".into()));
let clear = create_action(|_| clear_server_count());
create_action(|_: &()| adjust_server_count(-1, "dec dec goose".into()));
let inc =
create_action(|_: &()| adjust_server_count(1, "inc inc moose".into()));
let clear = create_action(|_: &()| clear_server_count());
#[cfg(not(feature = "ssr"))]
let multiplayer_value = {

View File

@@ -1,57 +1,54 @@
mod counters;
use leptos::*;
use actix_files::{Files};
use actix_web::*;
use crate::counters::*;
use leptos_actix::{generate_route_list, LeptosRoutes};
use crate::counters::*;
use actix_files::Files;
use actix_web::*;
use leptos::*;
use leptos_actix::{generate_route_list, LeptosRoutes};
#[get("/api/events")]
async fn counter_events() -> impl Responder {
use futures::StreamExt;
#[get("/api/events")]
async fn counter_events() -> impl Responder {
use crate::counters::ssr_imports::*;
use futures::StreamExt;
let stream =
futures::stream::once(async { crate::counters::get_server_count().await.unwrap_or(0) })
.chain(COUNT_CHANNEL.clone())
.map(|value| {
Ok(web::Bytes::from(format!(
"event: message\ndata: {value}\n\n"
))) as Result<web::Bytes>
});
HttpResponse::Ok()
.insert_header(("Content-Type", "text/event-stream"))
.streaming(stream)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// Explicit server function registration is no longer required
// on the main branch. On 0.3.0 and earlier, uncomment the lines
// below to register the server functions.
// _ = GetServerCount::register();
// _ = AdjustServerCount::register();
// _ = ClearServerCount::register();
// Setting this to None means we'll be using cargo-leptos and its env vars.
// when not using cargo-leptos None must be replaced with Some("Cargo.toml")
let conf = get_configuration(None).await.unwrap();
let addr = conf.leptos_options.site_addr;
let routes = generate_route_list(Counters);
HttpServer::new(move || {
let leptos_options = &conf.leptos_options;
let site_root = &leptos_options.site_root;
App::new()
.service(counter_events)
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), Counters)
.service(Files::new("/", site_root))
//.wrap(middleware::Compress::default())
})
.bind(&addr)?
.run()
.await
}
let stream = futures::stream::once(async {
crate::counters::get_server_count().await.unwrap_or(0)
})
.chain(COUNT_CHANNEL.clone())
.map(|value| {
Ok(web::Bytes::from(format!(
"event: message\ndata: {value}\n\n"
))) as Result<web::Bytes>
});
HttpResponse::Ok()
.insert_header(("Content-Type", "text/event-stream"))
.streaming(stream)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// Setting this to None means we'll be using cargo-leptos and its env vars.
// when not using cargo-leptos None must be replaced with Some("Cargo.toml")
let conf = get_configuration(None).await.unwrap();
let addr = conf.leptos_options.site_addr;
let routes = generate_route_list(Counters);
HttpServer::new(move || {
let leptos_options = &conf.leptos_options;
let site_root = &leptos_options.site_root;
App::new()
.service(counter_events)
.leptos_routes(
leptos_options.to_owned(),
routes.to_owned(),
Counters,
)
.service(Files::new("/", site_root))
//.wrap(middleware::Compress::default())
})
.bind(&addr)?
.run()
.await
}

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -2,6 +2,7 @@
name = "counter_without_macros"
version = "0.1.0"
edition = "2021"
rust-version = "1.75"
[profile.release]
codegen-units = 1
@@ -14,7 +15,7 @@ log = "0.4"
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2.84"
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.34"
pretty_assertions = "1.3.0"
rstest = "0.17.0"

View File

@@ -1,4 +1,4 @@
use leptos::{ev, html::*, *};
use leptos::{html::*, *};
/// A simple counter view.
// A component is really just a function call: it runs once to create the DOM and reactive system

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -1,4 +1,4 @@
use leptos::{For, *};
use leptos::*;
const MANY_COUNTERS: usize = 1000;

View File

@@ -2,6 +2,7 @@
name = "counters_stable"
version = "0.1.0"
edition = "2021"
rust-version = "1.75"
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
@@ -11,7 +12,7 @@ console_log = "1"
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2.87"
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.37"
pretty_assertions = "1.4.0"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -1,5 +1,6 @@
use leptos::{ev::click, html::AnyElement, *};
// no extra parameter
pub fn highlight(el: HtmlElement<AnyElement>) {
let mut highlighted = false;
@@ -14,6 +15,7 @@ pub fn highlight(el: HtmlElement<AnyElement>) {
});
}
// one extra parameter
pub fn copy_to_clipboard(el: HtmlElement<AnyElement>, content: &str) {
let content = content.to_string();
@@ -31,6 +33,35 @@ pub fn copy_to_clipboard(el: HtmlElement<AnyElement>, content: &str) {
});
}
// custom parameter
#[derive(Clone)]
pub struct Amount(usize);
impl From<usize> for Amount {
fn from(value: usize) -> Self {
Self(value)
}
}
// a 'default' value if no value is passed in
impl From<()> for Amount {
fn from(_: ()) -> Self {
Self(1)
}
}
// .into() will automatically be called on the parameter
pub fn add_dot(el: HtmlElement<AnyElement>, amount: Amount) {
_ = el.clone().on(click, move |_| {
el.set_inner_text(&format!(
"{}{}",
el.inner_text(),
".".repeat(amount.0)
))
})
}
#[component]
pub fn SomeComponent() -> impl IntoView {
view! {
@@ -46,6 +77,11 @@ pub fn App() -> impl IntoView {
view! {
<a href="#" use:copy_to_clipboard=data>"Copy \"" {data} "\" to clipboard"</a>
// automatically applies the directive to every root element in `SomeComponent`
<SomeComponent use:highlight />
// no value will default to `().into()`
<button use:add_dot>"Add a dot"</button>
// `5.into()` automatically called
<button use:add_dot=5>"Add 5 dots"</button>
}
}

View File

@@ -8,7 +8,7 @@ See the [Examples README](../README.md) for setup and run instructions.
## Testing
This project is configured to run start and stop of processes for integration tests wihtout the use of Cargo Leptos or Node.
This project is configured to run start and stop of processes for integration tests without the use of Cargo Leptos or Node.
## Quick Start

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -61,7 +61,7 @@ reload-port = 3001
end2end-cmd = "npx playwright test"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -1,5 +1,5 @@
use crate::errors::AppError;
use leptos::{logging::log, Errors, *};
use leptos::{logging::log, *};
#[cfg(feature = "ssr")]
use leptos_axum::ResponseOptions;

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -1,8 +0,0 @@
[env]
VERIFY_GTK = false
[tasks.verify-flow]
condition = { env_set = ["VERIFY_GTK"] }
[tasks.verify]
condition = { env_set = ["VERIFY_GTK"] }

View File

@@ -70,7 +70,7 @@ reload-port = 3001
end2end-cmd = "npx playwright test"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -62,16 +62,18 @@ pub fn Stories() -> impl IntoView {
}}
</span>
<span>"page " {page}</span>
<span class="page-link"
class:disabled=hide_more_link
aria-hidden=hide_more_link
>
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
aria-label="Next Page"
<Suspense>
<span class="page-link"
class:disabled=hide_more_link
aria-hidden=hide_more_link
>
"more >"
</a>
</span>
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
aria-label="Next Page"
>
"more >"
</a>
</span>
</Suspense>
</div>
<main class="news-list">
<div>

View File

@@ -71,7 +71,7 @@ reload-port = 3001
end2end-cmd = "npx playwright test"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -81,7 +81,7 @@ reload-port = 3001
end2end-cmd = "npx playwright test"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"

View File

@@ -1 +1,8 @@
extend = [{ path = "../cargo-make/main.toml" }]
extend = [
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/cargo-leptos.toml" },
]
[env]
CLIENT_PROCESS_NAME = "hackernews_islands"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -21,7 +21,7 @@ async fn main() {
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
logging::log!("listening on {}", addr);
println!("listening on {}", addr);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app.into_make_service())
.await

View File

@@ -11,21 +11,22 @@ codegen-units = 1
lto = true
[dependencies]
console_log = "1.0"
console_error_panic_hook = "0.1"
leptos = { path = "../../leptos", features = ["nightly"] }
leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_router = { path = "../../router", features = ["nightly"] }
log = "0.4"
simple_logger = "4.0"
serde = { version = "1.0", features = ["derive"] }
console_log = "1.0.0"
console_error_panic_hook = "0.1.7"
cfg-if = "1.0.0"
leptos = { version = "0.5", features = ["nightly"] }
leptos_axum = { version = "0.5", default-features = false, optional = true }
leptos_meta = { version = "0.5", features = ["nightly"] }
leptos_router = { version = "0.5", features = ["nightly"] }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1.0.148", features = ["derive"] }
tracing = "0.1"
gloo-net = { version = "0.4", features = ["http"] }
reqwest = { version = "0.11", features = ["json"] }
axum = { version = "0.7", default-features = false, optional = true }
tower = { version = "0.4", optional = true }
http = { version = "1.0", optional = true }
gloo-net = { version = "0.4.0", features = ["http"] }
reqwest = { version = "0.11.13", features = ["json"] }
axum = { version = "0.6", default-features = false, optional = true }
tower = { version = "0.4.13", optional = true }
http = { version = "0.2.11", optional = true }
web-sys = { version = "0.3", features = [
"AbortController",
"AbortSignal",
@@ -33,10 +34,10 @@ web-sys = { version = "0.3", features = [
"Response",
] }
wasm-bindgen = "0.2"
wasm-bindgen-futures = { version = "0.4", features = [
wasm-bindgen-futures = { version = "0.4.37", features = [
"futures-core-03-stream",
], optional = true }
axum-js-fetch = { version = "0.2", optional = true }
axum-js-fetch = { version = "0.2.1", optional = true }
lazy_static = "1.4.0"
[features]
@@ -77,7 +78,7 @@ reload-port = 3001
end2end-cmd = "npx playwright test"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"

View File

@@ -1 +1,8 @@
extend = [{ path = "../cargo-make/main.toml" }]
extend = [
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/deno-build.toml" },
]
[env]
CLIENT_PROCESS_NAME = "deno"

View File

@@ -2,6 +2,8 @@
This example uses the basic Hacker News example as its basis, but shows how to run the server side as WASM running in a JS environment. In this example, Deno is used as the runtime.
**NOTE**: This example is slightly out of date pending an update to [`axum-js-fetch`](https://github.com/seanaye/axum-js-fetch/), which was waiting on a version of `gloo-net` that uses `http` 1.0. It still works with Leptos 0.5 and Axum 0.6, but not with the versions of Leptos (0.6 and later) that support Axum 1.0.
## Server Side Rendering with Deno
To run the Deno version, run

View File

@@ -40,7 +40,7 @@ pub fn hydrate() {
#[cfg(feature = "ssr")]
mod ssr_imports {
use crate::App;
use axum::Router;
use axum::{routing::post, Router};
use leptos::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use log::{info, Level};
@@ -52,7 +52,7 @@ mod ssr_imports {
#[wasm_bindgen]
impl Handler {
pub async fn new() -> Self {
console_log::init_with_level(Level::Debug);
_ = console_log::init_with_level(Level::Debug);
console_error_panic_hook::set_once();
let leptos_options = LeptosOptions::builder()
@@ -62,8 +62,9 @@ mod ssr_imports {
let routes = generate_route_list(App);
// build our application with a route
let app: axum::Router = Router::new()
.leptos_routes(&leptos_options, routes, App)
let app: axum::Router<(), axum::body::Body> = Router::new()
.leptos_routes(&leptos_options, routes, || view! { <App/> })
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
.with_state(leptos_options);
info!("creating handler instance");

View File

@@ -5,13 +5,13 @@ extend = [
]
[tasks.build]
toolchain = "nightly"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["build-all-features", "--target", "wasm32-unknown-unknown"]
install_crate = "cargo-all-features"
[tasks.check]
toolchain = "nightly"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features", "--target", "wasm32-unknown-unknown"]
install_crate = "cargo-all-features"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -1,4 +1,4 @@
use leptos::{ev, *};
use leptos::*;
#[component]
pub fn CredentialsForm(

View File

@@ -16,7 +16,7 @@ impl AppState {
credentials: Credentials,
) -> Result<(), CreateUserError> {
let Credentials { email, password } = credentials;
let user_exists = self.users.get(&email).is_some();
let user_exists = self.users.contains_key(&email);
if user_exists {
return Err(CreateUserError::UserExists);
}
@@ -124,6 +124,7 @@ impl EmailAddress {
#[derive(Clone)]
pub struct CurrentUser {
pub email: EmailAddress,
#[allow(dead_code)] // possibly a lint regression, this is used at line 65
pub token: Uuid,
}

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly"
channel = "nightly-2024-03-31"

View File

@@ -1,3 +0,0 @@
/target
.env
.direnv

View File

@@ -1,92 +0,0 @@
[workspace]
members = ["todo_app_sqlite_pavex", "todo_app_sqlite_pavex_server_sdk", "todo_app_sqlite_pavex_server", "leptos_app"]
# By setting `todo_app_sqlite_pavex_server` as the default member, `cargo run` will default to running the server binary
# when executed from the root of the workspace.
# Otherwise, you would have to use `cargo run --bin api` to run the server binary.
default-members = ["todo_app_sqlite_pavex_server"]
resolver = "2"
# need to be applied only to wasm build
[profile.wasm_release]
codegen-units = 1
lto = true
opt-level = 'z'
[workspace.dependencies]
leptos = { version = "0.5", features = ["nightly"] }
leptos_meta = { version = "0.5", features = ["nightly"] }
leptos_router = { version = "0.5", features = ["nightly"] }
leptos_pavex = { version = "0.5" }
cfg_if = "1"
thiserror = "1"
# See https://github.com/akesson/cargo-leptos for documentation of all the parameters.
# A leptos project defines which workspace members
# that are used together frontend (lib) & server (bin)
[[workspace.metadata.leptos]]
# this name is used for the wasm, js and css file names
name = "start-pavex-workspace"
# the package in the workspace that contains the server binary (binary crate)
bin-package = "server"
# the package in the workspace that contains the frontend wasm binary (library crate)
lib-package = "leptos_frontend"
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
site-root = "target/site"
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
# Defaults to pkg
site-pkg-dir = "pkg"
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
style-file = "style/main.scss"
# Assets source dir. All files found here will be copied and synchronized to site-root.
# The assets-dir cannot have a sub directory with the same name/path as site-pkg-dir.
#
# Optional. Env: LEPTOS_ASSETS_DIR.
assets-dir = "public"
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
site-addr = "127.0.0.1:3000"
# The port to use for automatic reload monitoring
reload-port = 3001
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
# [Windows] for non-WSL use "npx.cmd playwright test"
# This binary name can be checked in Powershell with Get-Command npx
end2end-cmd = "npx playwright test"
end2end-dir = "end2end"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"
# The features to use when compiling the bin target
#
# Optional. Can be over-ridden with the command line parameter --bin-features
bin-features = []
# If the --no-default-features flag should be used when compiling the bin target
#
# Optional. Defaults to false.
bin-default-features = false
# The features to use when compiling the lib target
#
# Optional. Can be over-ridden with the command line parameter --lib-features
lib-features = []
# If the --no-default-features flag should be used when compiling the lib target
#
# Optional. Defaults to false.
lib-default-features = false

View File

@@ -1,71 +0,0 @@
# todo_app_sqlite_pavex
# Getting started
## Prerequisites
- Rust (see [here](https://www.rust-lang.org/tools/install) for instructions)
- `cargo-px`:
```bash
cargo install --locked cargo-px --version="~0.1"
```
- [Pavex](https://pavex.dev)
## Useful commands
`todo_app_sqlite_pavex` is built using the [Pavex](https://pavex.dev) web framework, which relies on code generation.
You need to use the `cargo px` command instead of `cargo`: it ensures that the
`todo_app_sqlite_pavex_server_sdk` crate is correctly regenerated when the
application blueprint changes.
`cargo px` is a wrapper around `cargo` that will automatically regenerate the
server SDK when needed. Check out its [documentation](https://github.com/LukeMathWalker/cargo-px)
for more details.
### Build
```bash
cargo px build
```
### Run
```bash
cargo px run
```
### Test
```bash
cargo px test
```
## Configuration
All configurable parameters are listed in `todo_app_sqlite_pavex/src/configuration.rs`.
Configuration values are loaded from two sources:
- Configuration files
- Environment variables
Environment variables take precedence over configuration files.
All configuration files are in the `todo_app_sqlite_pavex_server/configuration` folder.
The application can be run in three different profiles: `dev`, `test` and `prod`.
The settings that you want to share across all profiles should be placed in `todo_app_sqlite_pavex_server/configuration/base.yml`.
Profile-specific configuration files can be then used
to override or supply additional values on top of the default settings (e.g. `todo_app_sqlite_pavex_server/configuration/dev.yml`).
You can specify the app profile that you want to use by setting the `APP_PROFILE` environment variable; e.g.:
```bash
APP_PROFILE=prod cargo px run
```
for running the application with the `prod` profile.
By default, the `dev` profile is used since `APP_PROFILE` is set to `dev` in the `.env` file at the root of the project.
The `.env` file should not be committed to version control: it is meant to be used for local development only,
so that each developer can specify their own environment variables for secret values (e.g. database credentials)
that shouldn't be stored in configuration files (given their sensitive nature).

View File

@@ -1,119 +0,0 @@
{
"nodes": {
"cargo-pavex-git": {
"flake": false,
"locked": {
"lastModified": 1703610192,
"narHash": "sha256-+oM6VGRRt/DQdhEFWJFIpKfY29w72V0vRpud8NsOI7c=",
"owner": "LukeMathWalker",
"repo": "pavex",
"rev": "e302f99e3641a55fe5624ba6c8154ce64e732a89",
"type": "github"
},
"original": {
"owner": "LukeMathWalker",
"repo": "pavex",
"type": "github"
}
},
"cargo-px-git": {
"flake": false,
"locked": {
"lastModified": 1702137928,
"narHash": "sha256-FbwHEOQnIYKhxp4Ne9XBIUJXu1o+ak6y9MhzRenIW40=",
"owner": "LukeMathWalker",
"repo": "cargo-px",
"rev": "d1bb9075c4993130f31f31c95642567a2255bd8e",
"type": "github"
},
"original": {
"owner": "LukeMathWalker",
"repo": "cargo-px",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1703499205,
"narHash": "sha256-lF9rK5mSUfIZJgZxC3ge40tp1gmyyOXZ+lRY3P8bfbg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e1fa12d4f6c6fe19ccb59cac54b5b3f25e160870",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"cargo-pavex-git": "cargo-pavex-git",
"cargo-px-git": "cargo-px-git",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1703643208,
"narHash": "sha256-UL4KO8JxnD5rOycwHqBAf84lExF1/VnYMDC7b/wpPDU=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "ce117f3e0de8262be8cd324ee6357775228687cf",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -1,129 +0,0 @@
{
description = "Build Pavex tools";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
cargo-px-git = {
url = "github:/LukeMathWalker/cargo-px";
flake = false;
};
cargo-pavex-git = {
url = "github:LukeMathWalker/pavex";
flake = false;
};
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs = {
nixpkgs.follows = "nixpkgs";
flake-utils.follows = "flake-utils";
};
};
};
outputs = { self, nixpkgs, flake-utils, rust-overlay, ... } @inputs:
flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ (import rust-overlay) ];
};
inherit (pkgs) lib;
rustTarget = pkgs.rust-bin.selectLatestNightlyWith( toolchain: toolchain.default.override {
extensions = [ "rust-src" "rust-analyzer" "rustc-codegen-cranelift-preview" "rust-docs-json"];
targets = [ "wasm32-unknown-unknown" ];
});
cargo-pavex_cli-git = pkgs.rustPlatform.buildRustPackage rec {
pname = "cargo-pavex-cli";
version = "0.2.22";
#buildFeatures = ["no_downloads"]; # cargo-leptos will try to download Ruby and other things without this feature
src = inputs.cargo-pavex-git;
sourceRoot = "source/libs";
cargoLock = {
lockFile = inputs.cargo-pavex-git + "/libs/Cargo.lock";
outputHashes = {
"matchit-0.7.3" = "sha256-1bhbWvLlDb6/UJ4j2FqoG7j3DD1dTOLl6RaiY9kasmQ=";
#"pavex-0.1.0" = "sha256-NC7T1pcXJiWPtAWeiMUNzf2MUsYaRYxjLIL9fCqhExo=";
};
};
#buildAndTestSubdir = "libs";
cargoSha256 = "";
nativeBuildInputs = [pkgs.pkg-config pkgs.openssl pkgs.git];
buildInputs = with pkgs;
[openssl pkg-config git]
++ lib.optionals stdenv.isDarwin [
Security
];
doCheck = false; # integration tests depend on changing cargo config
meta = with lib; {
description = "An easy-to-use Rust framework for building robust and performant APIs";
homepage = "https://github.com/LukeMatthewWalker/pavex";
changelog = "https://github.com/LukeMatthewWalker/pavex/blob/v${version}/CHANGELOG.md";
license = with licenses; [mit];
maintainers = with maintainers; [benwis];
};
};
cargo-px-git = pkgs.rustPlatform.buildRustPackage rec {
pname = "cargo-px";
version = "0.2.22";
#buildFeatures = ["no_downloads"]; # cargo-leptos will try to download Ruby and other things without this feature
src = inputs.cargo-px-git;
cargoSha256 ="sha256-+pyeqh0IoZ1JMgbhWxhEJw1MPgG7XeocVrqJoSNjgDA=";
nativeBuildInputs = [pkgs.pkg-config pkgs.openssl pkgs.git];
buildInputs = with pkgs;
[openssl pkg-config git]
++ lib.optionals stdenv.isDarwin [
Security
];
doCheck = false; # integration tests depend on changing cargo config
meta = with lib; {
description = "A cargo subcommand that extends cargo's capabilities when it comes to code generation.";
homepage = "https://github.com/LukeMatthewWalker/cargo-px";
changelog = "https://github.com/LukeMatthewWalker/cargo-px/blob/v${version}/CHANGELOG.md";
license = with licenses; [mit];
maintainers = with maintainers; [benwis];
};
};
in
{
devShells.default = pkgs.mkShell {
# Extra inputs can be added here
nativeBuildInputs = with pkgs; [
#rustTarget
rustup
openssl
pkg-config
clang
tailwindcss
mold-wrapped
cargo-px-git
cargo-pavex_cli-git
];
#RUST_SRC_PATH = "${rustTarget}/lib/rustlib/src/rust/library";
MOLD_PATH = "${pkgs.mold-wrapped}/bin/mold";
shellHook = ''
sed -i -e '/rustflags = \["-C", "link-arg=-fuse-ld=/ s|ld=.*|ld=${pkgs.mold-wrapped}/bin/mold"]|' .cargo/config.toml
'';
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
};
});
}

View File

@@ -1,21 +0,0 @@
[package]
name = "leptos_app"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
leptos.workspace = true
leptos_meta.workspace = true
leptos_router.workspace = true
leptos_pavex = { workspace = true, optional = true }
#http.workspace = true
cfg_if.workspace = true
thiserror.workspace = true
[features]
default = []
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = ["leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", "dep:leptos_pavex"]

View File

@@ -1,73 +0,0 @@
use cfg_if::cfg_if;
use http::status::StatusCode;
use leptos::*;
#[cfg(feature = "ssr")]
use leptos_axum::ResponseOptions;
use thiserror::Error;
#[derive(Clone, Debug, Error)]
pub enum AppError {
#[error("Not Found")]
NotFound,
}
impl AppError {
pub fn status_code(&self) -> StatusCode {
match self {
AppError::NotFound => StatusCode::NOT_FOUND,
}
}
}
// A basic function to display errors served by the error boundaries.
// Feel free to do more complicated things here than just displaying the error.
#[component]
pub fn ErrorTemplate(
#[prop(optional)] outside_errors: Option<Errors>,
#[prop(optional)] errors: Option<RwSignal<Errors>>,
) -> impl IntoView {
let errors = match outside_errors {
Some(e) => create_rw_signal(e),
None => match errors {
Some(e) => e,
None => panic!("No Errors found and we expected errors!"),
},
};
// Get Errors from Signal
let errors = errors.get_untracked();
// Downcast lets us take a type that implements `std::error::Error`
let errors: Vec<AppError> = errors
.into_iter()
.filter_map(|(_k, v)| v.downcast_ref::<AppError>().cloned())
.collect();
println!("Errors: {errors:#?}");
// Only the response code for the first error is actually sent from the server
// this may be customized by the specific application
cfg_if! { if #[cfg(feature="ssr")] {
let response = use_context::<ResponseOptions>();
if let Some(response) = response {
response.set_status(errors[0].status_code());
}
}}
view! {
<h1>{if errors.len() > 1 { "Errors" } else { "Error" }}</h1>
<For
// a function that returns the items we're iterating over; a signal is fine
each=move || { errors.clone().into_iter().enumerate() }
// a unique key for each item as a reference
key=|(index, _error)| *index
// renders each item to a view
children=move |error| {
let error_string = error.1.to_string();
let error_code = error.1.status_code();
view! {
<h2>{error_code.to_string()}</h2>
<p>"Error: " {error_string}</p>
}
}
/>
}
}

View File

@@ -1,45 +0,0 @@
use crate::error_template::{AppError, ErrorTemplate};
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
pub mod error_template;
#[component]
pub fn App() -> impl IntoView {
// Provides context that manages stylesheets, titles, meta tags, etc.
provide_meta_context();
view! {
<Stylesheet id="leptos" href="/pkg/start-axum-workspace.css"/>
// sets the document title
<Title text="Welcome to Leptos"/>
// content for this welcome page
<Router fallback=|| {
let mut outside_errors = Errors::default();
outside_errors.insert_with_default_key(AppError::NotFound);
view! { <ErrorTemplate outside_errors/> }.into_view()
}>
<main>
<Routes>
<Route path="" view=HomePage/>
</Routes>
</main>
</Router>
}
}
/// Renders the home page of your application.
#[component]
fn HomePage() -> impl IntoView {
// Creates a reactive value to update the button
let (count, set_count) = create_signal(0);
let on_click = move |_| set_count.update(|count| *count += 1);
view! {
<h1>"Welcome to Leptos on Pavex!"</h1>
<button on:click=on_click>"Click Me: " {count}</button>
}
}

View File

@@ -1,8 +0,0 @@
[package]
name = "leptos_front"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@@ -1,13 +0,0 @@
use leptos::*;
use leptos_app::*;
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
pub fn hydrate() {
// initializes logging using the `log` crate
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::mount_to_body(App);
}

View File

@@ -1,4 +0,0 @@
body {
font-family: sans-serif;
text-align: center;
}

View File

@@ -1,22 +0,0 @@
[package]
name = "todo_app_sqlite_pavex"
version = "0.1.0"
edition = "2021"
[[bin]]
path = "src/bin/bp.rs"
name = "bp"
[dependencies]
cargo_px_env = "0.1"
pavex = { git = "https://github.com/LukeMathWalker/pavex", branch = "main" }
pavex_cli_client = { git = "https://github.com/LukeMathWalker/pavex", branch = "main" }
tracing = "0.1"
# Configuration
serde = { version = "1", features = ["derive"] }
serde-aux = "4"
# Leptos
leptos_pavex.workspace = true

View File

@@ -1,17 +0,0 @@
use cargo_px_env::generated_pkg_manifest_path;
use todo_app_sqlite_pavex::blueprint;
use pavex_cli_client::Client;
use std::error::Error;
/// Generate the `todo_app_sqlite_pavex_server_sdk` crate using Pavex's CLI.
///
/// Pavex will automatically wire all our routes, constructors and error handlers
/// into the a "server SDK" that can be used by the final API server binary to launch
/// the application.
fn main() -> Result<(), Box<dyn Error>> {
let generated_dir = generated_pkg_manifest_path()?.parent().unwrap().into();
Client::new()
.generate(blueprint(), generated_dir)
.execute()?;
Ok(())
}

View File

@@ -1,98 +0,0 @@
use leptos_pavex::{LeptosOptions, RouteListing};
use pavex::{
blueprint::{
constructor::{CloningStrategy, Lifecycle},
router::{ANY, GET},
Blueprint,
},
f,
};
/// The main blueprint, containing all the routes, constructors and error handlers
/// required by our API.
pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
register_common_constructors(&mut bp);
bp.constructor(
f!(crate::user_agent::UserAgent::extract),
Lifecycle::RequestScoped,
)
.error_handler(f!(crate::user_agent::invalid_user_agent));
add_telemetry_middleware(&mut bp);
bp.route(GET, "/test/ping", f!(crate::routes::status::ping));
bp.route(GET, "/test/greet/:name", f!(crate::routes::greet::greet));
// Handle all /api requests as those are Leptos server fns
bp.route(ANY, "/api/*fn_name", f!(leptos_pavex::handle_server_fns));
bp.route(ANY, "/");
bp.fallback(f!(file_handler));
bp
}
/// Common constructors used by all routes.
fn register_common_constructors(bp: &mut Blueprint) {
// Configuration Options
bp.constructor(
f!(crate::leptos::get_cargo_leptos_conf(), Lifecycle::Singleton),
Lifecycle::Singleton,
);
// List of Routes
bp.constructor(
f!(crate::leptos::get_app_route_listing(), Lifecycle::Singleton),
Lifecycle::Singleton,
);
bp.constructor(
f!(leptos_pavex::PavexRequest::extract),
LifeCycle::RequestScoped,
);
// Query parameters
bp.constructor(
f!(pavex::request::query::QueryParams::extract),
Lifecycle::RequestScoped,
)
.error_handler(f!(
pavex::request::query::errors::ExtractQueryParamsError::into_response
));
// Route parameters
bp.constructor(
f!(pavex::request::route::RouteParams::extract),
Lifecycle::RequestScoped,
)
.error_handler(f!(
pavex::request::route::errors::ExtractRouteParamsError::into_response
));
// Json body
bp.constructor(
f!(pavex::request::body::JsonBody::extract),
Lifecycle::RequestScoped,
)
.error_handler(f!(
pavex::request::body::errors::ExtractJsonBodyError::into_response
));
bp.constructor(
f!(pavex::request::body::BufferedBody::extract),
Lifecycle::RequestScoped,
)
.error_handler(f!(
pavex::request::body::errors::ExtractBufferedBodyError::into_response
));
bp.constructor(
f!(<pavex::request::body::BodySizeLimit as std::default::Default>::default),
Lifecycle::RequestScoped,
);
}
/// Add the telemetry middleware, as well as the constructors of its dependencies.
fn add_telemetry_middleware(bp: &mut Blueprint) {
bp.constructor(
f!(crate::telemetry::RootSpan::new),
Lifecycle::RequestScoped,
)
.cloning(CloningStrategy::CloneIfNecessary);
bp.wrap(f!(crate::telemetry::logger));
}

View File

@@ -1,32 +0,0 @@
use pavex::server::IncomingStream;
use serde_aux::field_attributes::deserialize_number_from_string;
use std::net::SocketAddr;
#[derive(serde::Deserialize)]
/// The top-level configuration, holding all the values required
/// to configure the entire application.
pub struct Config {
pub server: ServerConfig,
}
#[derive(serde::Deserialize, Clone)]
/// Configuration for the HTTP server used to expose our API
/// to users.
pub struct ServerConfig {
/// The port that the server must listen on.
#[serde(deserialize_with = "deserialize_number_from_string")]
pub port: u16,
/// The network interface that the server must be bound to.
///
/// E.g. `0.0.0.0` for listening to incoming requests from
/// all sources.
pub ip: std::net::IpAddr,
}
impl ServerConfig {
/// Bind a TCP listener according to the specified parameters.
pub async fn listener(&self) -> Result<IncomingStream, std::io::Error> {
let addr = SocketAddr::new(self.ip, self.port);
IncomingStream::bind(addr).await
}
}

View File

@@ -1,45 +0,0 @@
use app::error_template::AppError;
use app::error_template::ErrorTemplate;
use app::App;
use axum::response::Response as AxumResponse;
use axum::{
body::{boxed, Body, BoxBody},
extract::State,
http::{Request, Response, StatusCode, Uri},
response::IntoResponse,
};
use leptos::*;
use tower::ServiceExt;
use tower_http::services::ServeDir;
pub async fn file_and_error_handler(
uri: Uri,
State(options): State<LeptosOptions>,
req: Request<Body>,
) -> AxumResponse {
let root = options.site_root.clone();
let res = get_static_file(uri.clone(), &root).await.unwrap();
if res.status() == StatusCode::OK {
res.into_response()
} else {
let handler = leptos_axum::render_app_to_stream(options.to_owned(), move || view! { <App/> });
handler(req).await.into_response()
}
}
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<BoxBody>, (StatusCode, String)> {
let req = Request::builder()
.uri(uri.clone())
.body(Body::empty())
.unwrap();
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
// This path is relative to the cargo root
match ServeDir::new(root).oneshot(req).await {
Ok(res) => Ok(res.map(boxed)),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {err}"),
)),
}
}

View File

@@ -1,19 +0,0 @@
use leptos::{get_configuration, leptos_config::ConfFile};
use leptos_pavex::generate_route_list;
use leptos_router::RouteListing;
use pavex::{
http::header::{ToStrError, USER_AGENT},
request::RequestHead,
response::Response,
};
/// Easier to do this to avoid having to register things with Blueprints
/// Provide LeptosOptions via env vars provided by cargo-leptos or the user
pub fn get_cargo_leptos_conf() -> ConfFile {
get_configuration(None)
}
/// Generate all possible non server fn routes for our app
pub fn get_app_route_listing() -> Vec<RouteListing> {
generate_route_list(TodoApp)
}

View File

@@ -1,7 +0,0 @@
mod blueprint;
pub mod configuration;
pub mod leptos;
pub mod routes;
pub mod telemetry;
pub mod user_agent;
pub use blueprint::blueprint;

View File

@@ -1,21 +0,0 @@
use crate::user_agent::UserAgent;
use pavex::{request::route::RouteParams, response::Response};
#[RouteParams]
pub struct GreetParams {
pub name: String,
}
pub fn greet(
params: RouteParams<GreetParams>,
user_agent: UserAgent,
) -> Response {
if let UserAgent::Unknown = user_agent {
return Response::unauthorized()
.set_typed_body("You must provide a `User-Agent` header")
.box_body();
}
let GreetParams { name } = params.0;
Response::ok()
.set_typed_body(format!("Hello, {name}!"))
.box_body()
}

View File

@@ -1,3 +0,0 @@
pub mod greet;
pub mod status;

View File

@@ -1,7 +0,0 @@
use pavex::http::StatusCode;
/// Respond with a `200 OK` status code to indicate that the server is alive
/// and ready to accept new requests.
pub fn ping() -> StatusCode {
StatusCode::OK
}

View File

@@ -1,84 +0,0 @@
use pavex::request::route::MatchedRouteTemplate;
use pavex::http::Version;
use pavex::middleware::Next;
use pavex::request::RequestHead;
use pavex::response::Response;
use std::borrow::Cow;
use std::future::IntoFuture;
use tracing::Instrument;
/// A logging middleware that wraps the request pipeline in the root span.
/// It takes care to record key information about the request and the response.
pub async fn logger<T>(next: Next<T>, root_span: RootSpan) -> Response
where
T: IntoFuture<Output = Response>,
{
let response = next
.into_future()
.instrument(root_span.clone().into_inner())
.await;
root_span.record_response_data(&response);
response
}
/// A root span is the top-level *logical* span for an incoming request.
///
/// It is not necessarily the top-level *physical* span, as it may be a child of
/// another span (e.g. a span representing the underlying HTTP connection).
///
/// We use the root span to attach as much information as possible about the
/// incoming request, and to record the final outcome of the request (success or
/// failure).
#[derive(Debug, Clone)]
pub struct RootSpan(tracing::Span);
impl RootSpan {
/// Create a new root span for the given request.
///
/// We follow OpenTelemetry's HTTP semantic conventions as closely as
/// possible for field naming.
pub fn new(request_head: &RequestHead, matched_route: MatchedRouteTemplate) -> Self {
let user_agent = request_head
.headers
.get("User-Agent")
.map(|h| h.to_str().unwrap_or_default())
.unwrap_or_default();
let span = tracing::info_span!(
"HTTP request",
http.method = %request_head.method,
http.flavor = %http_flavor(request_head.version),
user_agent.original = %user_agent,
http.response.status_code = tracing::field::Empty,
http.route = %matched_route,
http.target = %request_head.uri.path_and_query().map(|p| p.as_str()).unwrap_or(""),
);
Self(span)
}
pub fn record_response_data(&self, response: &Response) {
self.0
.record("http.response.status_code", &response.status().as_u16());
}
/// Get a reference to the underlying [`tracing::Span`].
pub fn inner(&self) -> &tracing::Span {
&self.0
}
/// Deconstruct the root span into its underlying [`tracing::Span`].
pub fn into_inner(self) -> tracing::Span {
self.0
}
}
fn http_flavor(version: Version) -> Cow<'static, str> {
match version {
Version::HTTP_09 => "0.9".into(),
Version::HTTP_10 => "1.0".into(),
Version::HTTP_11 => "1.1".into(),
Version::HTTP_2 => "2.0".into(),
Version::HTTP_3 => "3.0".into(),
other => format!("{other:?}").into(),
}
}

View File

@@ -1,27 +0,0 @@
use pavex::{
http::header::{ToStrError, USER_AGENT},
request::RequestHead,
response::Response,
};
pub enum UserAgent {
/// No User-Agent header was provided
Unknown,
/// The value of the 'User-Agent' header for the incoming request
Known(String),
}
impl UserAgent {
pub fn extract(request_head: &RequestHead) -> Result<Self, ToStrError> {
let Some(user_agent) = request_head.headers.get(USER_AGENT) else {
return Ok(UserAgent::Unknown);
};
user_agent.to_str().map(|s| UserAgent::Known(s.into()))
}
}
pub fn invalid_user_agent(_e: &ToStrError) -> Response {
Response::bad_request()
.set_typed_body("The `User-Agent` header must be a valid UTF-8 string")
.box_body()
}

View File

@@ -1,29 +0,0 @@
[package]
name = "todo_app_sqlite_pavex_server"
version = "0.1.0"
edition = "2021"
[[bin]]
path = "src/bin/api.rs"
name = "api"
[dependencies]
anyhow = "1"
pavex = { git = "https://github.com/LukeMathWalker/pavex", branch = "main" }
tokio = { version = "1", features = ["full"] }
todo_app_sqlite_pavex_server_sdk = { path = "../todo_app_sqlite_pavex_server_sdk" }
todo_app_sqlite_pavex = { path = "../todo_app_sqlite_pavex" }
# Configuration
dotenvy = "0.15"
figment = { version = "0.10", features = ["env", "yaml"] }
serde = { version = "1", features = ["derive"]}
# Telemetry
tracing = "0.1"
tracing-bunyan-formatter = "0.3"
tracing-panic = "0.1"
tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "registry", "smallvec", "std", "tracing-log"] }
[dev-dependencies]
reqwest = "0.11"

View File

@@ -1,3 +0,0 @@
server:
ip: "0.0.0.0"
port: 8000

View File

@@ -1,6 +0,0 @@
# This file contains the configuration for the dev environment.
# None of the values here are actually secret, so it's fine
# to commit this file to the repository.
server:
ip: "127.0.0.1"
port: 8000

View File

@@ -1,3 +0,0 @@
server:
ip: "0.0.0.0"
port: 8000

View File

@@ -1,8 +0,0 @@
# This file contains the configuration for the API when spawned
# in black-box tests.
# None of the values here are actually secret, so it's fine
# to commit this file to the repository.
server:
ip: "127.0.0.1"
# The OS will assign a random port to the test server.
port: 0

View File

@@ -1,49 +0,0 @@
use anyhow::Context;
use todo_app_sqlite_pavex_server::{
configuration::load_configuration,
telemetry::{get_subscriber, init_telemetry},
};
use todo_app_sqlite_pavex_server_sdk::{build_application_state, run};
use pavex::server::Server;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let subscriber = get_subscriber("todo_app_sqlite_pavex".into(), "info".into(), std::io::stdout);
init_telemetry(subscriber)?;
// We isolate all the server setup and launch logic in a separate function
// in order to have a single choke point where we make sure to log fatal errors
// that will cause the application to exit.
if let Err(e) = _main().await {
tracing::error!(
error.msg = %e,
error.error_chain = ?e,
"The application is exiting due to an error"
)
}
Ok(())
}
async fn _main() -> anyhow::Result<()> {
// Load environment variables from a .env file, if it exists.
let _ = dotenvy::dotenv();
let config = load_configuration(None)?;
let application_state = build_application_state()
.await;
let tcp_listener = config
.server
.listener()
.await
.context("Failed to bind the server TCP listener")?;
let address = tcp_listener
.local_addr()
.context("The server TCP listener doesn't have a local socket address")?;
let server_builder = Server::new().listen(tcp_listener);
tracing::info!("Starting to listen for incoming requests at {}", address);
run(server_builder, application_state).await;
Ok(())
}

View File

@@ -1,140 +0,0 @@
use std::env::VarError;
use anyhow::Context;
use todo_app_sqlite_pavex::configuration::Config;
use figment::{
providers::{Env, Format, Yaml},
Figment,
};
/// Retrieve the application configuration by merging together multiple configuration sources.
///
/// # Application profiles
///
/// We use the concept of application profiles to allow for
/// different configuration values depending on the type of environment
/// the application is running in.
///
/// We don't rely on `figment`'s built-in support for profiles because
/// we want to make sure that values for different profiles are not co-located in
/// the same configuration file.
/// This makes it easier to avoid leaking sensitive information by mistake (e.g.
/// by committing configuration values for the `dev` profile to the repository).
///
/// You primary mechanism to specify the desired application profile is the `APP_PROFILE`
/// environment variable.
/// You can pass a `default_profile` value that will be used if the environment variable
/// is not set.
///
/// # Hierarchy
///
/// The configuration sources are:
///
/// 1. `base.yml` - Contains the default configuration values, common to all profiles.
/// 2. `<profile>.yml` - Contains the configuration values specific to the desired profile.
/// 3. Environment variables - Contains the configuration values specific to the current environment.
///
/// The configuration sources are listed in priority order, i.e.
/// the last source in the list will override any previous source.
///
/// For example, if the same configuration key is defined in both
/// the YAML file and the environment, the value from the environment
/// will be used.
pub fn load_configuration(
default_profile: Option<ApplicationProfile>,
) -> Result<Config, anyhow::Error> {
let application_profile = load_app_profile(default_profile)
.context("Failed to load the desired application profile")?;
let configuration_dir = {
let manifest_dir = env!(
"CARGO_MANIFEST_DIR",
"`CARGO_MANIFEST_DIR` was not set. Are you using a custom build system?"
);
std::path::Path::new(manifest_dir).join("configuration")
};
let base_filepath = configuration_dir.join("base.yml");
let profile_filename = format!("{}.yml", application_profile.as_str());
let profile_filepath = configuration_dir.join(profile_filename);
let figment = Figment::new()
.merge(Yaml::file(base_filepath))
.merge(Yaml::file(profile_filepath))
.merge(Env::prefixed("APP_"));
let configuration: Config = figment
.extract()
.context("Failed to load hierarchical configuration")?;
Ok(configuration)
}
/// Load the application profile from the `APP_PROFILE` environment variable.
fn load_app_profile(
default_profile: Option<ApplicationProfile>,
) -> Result<ApplicationProfile, anyhow::Error> {
static PROFILE_ENV_VAR: &str = "APP_PROFILE";
match std::env::var(PROFILE_ENV_VAR) {
Ok(raw_value) => raw_value.parse().with_context(|| {
format!("Failed to parse the `{PROFILE_ENV_VAR}` environment variable")
}),
Err(VarError::NotPresent) if default_profile.is_some() => Ok(default_profile.unwrap()),
Err(e) => Err(anyhow::anyhow!(e).context(format!(
"Failed to read the `{PROFILE_ENV_VAR}` environment variable"
))),
}
}
/// The application profile, i.e. the type of environment the application is running in.
/// See [`load_configuration`] for more details.
pub enum ApplicationProfile {
/// Test profile.
///
/// This is the profile used by the integration test suite.
Test,
/// Local development profile.
///
/// This is the profile you should use when running the application locally
/// for exploratory testing.
///
/// The corresponding configuration file is `dev.yml` and it's *never* committed to the repository.
Dev,
/// Production profile.
///
/// This is the profile you should use when running the application in production—e.g.
/// when deploying it to a staging or production environment, exposed to live traffic.
///
/// The corresponding configuration file is `prod.yml`.
/// It's committed to the repository, but it's meant to contain exclusively
/// non-sensitive configuration values.
Prod,
}
impl ApplicationProfile {
/// Return the environment as a string.
pub fn as_str(&self) -> &'static str {
match self {
ApplicationProfile::Test => "test",
ApplicationProfile::Dev => "dev",
ApplicationProfile::Prod => "prod",
}
}
}
impl std::str::FromStr for ApplicationProfile {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"test" => Ok(ApplicationProfile::Test),
"dev" | "development" => Ok(ApplicationProfile::Dev),
"prod" | "production" => Ok(ApplicationProfile::Prod),
s => Err(anyhow::anyhow!(
"`{}` is not a valid application profile.\nValid options are: `test`, `dev`, `prod`.",
s
)),
}
}
}

View File

@@ -1,2 +0,0 @@
pub mod configuration;
pub mod telemetry;

View File

@@ -1,40 +0,0 @@
use anyhow::Context;
use tracing::subscriber::set_global_default;
use tracing::Subscriber;
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
use tracing_subscriber::fmt::MakeWriter;
use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry};
/// Perform all the required setup steps for our telemetry:
///
/// - Register a subscriber as global default to process span data
/// - Register a panic hook to capture any panic and record its details
///
/// It should only be called once!
pub fn init_telemetry(subscriber: impl Subscriber + Sync + Send) -> Result<(), anyhow::Error> {
std::panic::set_hook(Box::new(tracing_panic::panic_hook));
set_global_default(subscriber).context("Failed to set a `tracing` global subscriber")
}
/// Compose multiple layers into a `tracing`'s subscriber.
///
/// # Implementation Notes
///
/// We are using `impl Subscriber` as return type to avoid having to spell out the actual
/// type of the returned subscriber, which is indeed quite complex.
pub fn get_subscriber<Sink>(
application_name: String,
default_env_filter: String,
sink: Sink,
) -> impl Subscriber + Sync + Send
where
Sink: for<'a> MakeWriter<'a> + Send + Sync + 'static,
{
let env_filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(default_env_filter));
let formatting_layer = BunyanFormattingLayer::new(application_name, sink);
Registry::default()
.with(env_filter)
.with(JsonStorageLayer)
.with(formatting_layer)
}

View File

@@ -1,37 +0,0 @@
use crate::helpers::TestApi;
use pavex::http::StatusCode;
#[tokio::test]
async fn greet_happy_path() {
let api = TestApi::spawn().await;
let name = "Ursula";
let response = api
.api_client
.get(&format!("{}/api/greet/{name}", &api.api_address))
.header("User-Agent", "Test runner")
.send()
.await
.expect("Failed to execute request.");
assert_eq!(response.status().as_u16(), StatusCode::OK.as_u16());
assert_eq!(response.text().await.unwrap(), "Hello, Ursula!");
}
#[tokio::test]
async fn non_utf8_agent_is_rejected() {
let api = TestApi::spawn().await;
let name = "Ursula";
let response = api
.api_client
.get(&format!("{}/api/greet/{name}", &api.api_address))
.header("User-Agent", b"hello\xfa".as_slice())
.send()
.await
.expect("Failed to execute request.");
assert_eq!(response.status().as_u16(), StatusCode::BAD_REQUEST.as_u16());
assert_eq!(
response.text().await.unwrap(),
"The `User-Agent` header must be a valid UTF-8 string"
);
}

View File

@@ -1,52 +0,0 @@
use todo_app_sqlite_pavex_server::configuration::{load_configuration, ApplicationProfile};
use todo_app_sqlite_pavex_server_sdk::{build_application_state, run};
use todo_app_sqlite_pavex::configuration::Config;
use pavex::server::Server;
pub struct TestApi {
pub api_address: String,
pub api_client: reqwest::Client,
}
impl TestApi {
pub async fn spawn() -> Self {
let config = Self::get_config();
let application_state = build_application_state().await;
let tcp_listener = config
.server
.listener()
.await
.expect("Failed to bind the server TCP listener");
let address = tcp_listener
.local_addr()
.expect("The server TCP listener doesn't have a local socket address");
let server_builder = Server::new().listen(tcp_listener);
tokio::spawn(async move {
run(server_builder, application_state).await
});
TestApi {
api_address: format!("http://{}:{}", config.server.ip, address.port()),
api_client: reqwest::Client::new(),
}
}
fn get_config() -> Config {
load_configuration(Some(ApplicationProfile::Test)).expect("Failed to load test configuration")
}
}
/// Convenient methods for calling the API under test.
impl TestApi {
pub async fn get_ping(&self) -> reqwest::Response
{
self.api_client
.get(&format!("{}/api/ping", &self.api_address))
.send()
.await
.expect("Failed to execute request.")
}
}

Some files were not shown because too many files have changed in this diff Show More