Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat:v0.3.0 #2

Merged
merged 3 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 81 additions & 17 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,93 @@
## 0.2.0
## 0.3.0 (Minor)

- #### Minor Release
- **NEW** Multiple Routers with `basePath`

- Improved CLI
```dart

- Added a new command H4 **start** which starts your app locally.
void main() async {
var app = createApp(
port: 5173,
onRequest: (event) {
// PER REQUEST local state😻
event.context["user"] = 'Ogunlepon';

setResponseHeader(event,
header: HttpHeaders.contentTypeHeader,
value: 'text/html; charset=utf-8');
},
afterResponse: (event) => {},
);

```powershell
h4 start
```
var router = createRouter();
var apiRouter = createRouter();

_or_
app.use(router, basePath: '/');
app.use(apiRouter, basePath: '/api');

```powershell
dart pub global run h4:start
```
router.get("/vamos/:id/base/:studentId", (event) {
return event.params;
});

apiRouter.get("/signup", (event) async {
var formData = await readFormData(event);

var username = formData.get('username');
var password = formData.get('password');

// userService.signup(username, password);
event.statusCode = 201;

return 'Hi from /api with $username, $password';
});
}
```

- **NEW** `readFormData` utility - familiar formdata parsing API

```dart
apiRouter.get("/signup", (event) async {
var formData = await readFormData(event);

var username = formData.get('username');
var password = formData.get('password');

// ALSO included..
var allNames = formdata.getAll('names') // return List<String>

userService.signup(username, password);
event.statusCode = 201;

return 'Hi from /api';
});
```

- **NEW** `getRequestIp` and `getRequestOrigin` utilities
- **PATCHED** `H4Event` params now correctly parses more than one param in the route string. e.g
`'/user/:userId/profile/:projectId'`
- **PATCHED** all bugs in the behaviour of param routes.

## 0.2.0 (Minor)

- Added the `start` command to the CLI, `H4 start` which starts your app locally.

```powershell
h4 start
```

_or_

```powershell
dart pub global run h4:start
```

#### --dev flag
#### --dev flag

- Run the command with the --dev flag to restart the server when you make changes to your files
- Run the command with the --dev flag to restart the server when you make changes to your files

- ### New Utilities
- getQuery
- setResponseHeader
- getHeader
- ### New Utilities
- `getQuery`
- `setResponseHeader`
- `getHeader`

## 0.1.4

Expand Down
26 changes: 22 additions & 4 deletions bin/main.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import 'dart:io';

import 'package:h4/create.dart';
import 'package:h4/utils/req_utils.dart';
import 'package:h4/utils/set_response_header.dart';

void main() async {
var app = createApp(
port: 5173,
onRequest: (event) {
// PER REQUEST local state😻
event.context["user"] = 'Ogunlepon';

setResponseHeader(event,
header: HttpHeaders.contentTypeHeader,
value: 'text/html; charset=utf-8');
},
afterResponse: (event) => {},
);
Expand All @@ -16,11 +24,21 @@ void main() async {
app.use(router, basePath: '/');
app.use(apiRouter, basePath: '/api');

router.get("/", (event) {
return 'Hello from /';
router.get("/vamos/:id/base/:studentId", (event) {
return event.params;
});

apiRouter.get("/", (event) {
return 'Hi from /api';
apiRouter.get("/signup", (event) async {
var formData = await readFormData(event);

var username = formData.get('username');
var password = formData.get('password');

print(getRequestIp(event));

// userService.signup(username, password);
event.statusCode = 201;

return 'Hi from /api with $username, $password';
});
}
2 changes: 0 additions & 2 deletions bin/run.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ void main(List<String> arguments) async {
print(body["map"]);
var header = getHeader(event, HttpHeaders.userAgentHeader);
var query = getQueryParams(event);
setResponseHeader(event, HttpHeaders.contentTypeHeader,
value: 'application/json');
return [header, body, query, event.params];
});

Expand Down
4 changes: 2 additions & 2 deletions lib/src/error_middleware.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ Function(HttpRequest) defineErrorHandler(ErrorHandler handler,

event.statusCode = statusCode;

setResponseHeader(event, HttpHeaders.contentTypeHeader,
value: 'application/json');
setResponseHeader(event,
header: HttpHeaders.contentTypeHeader, value: 'application/json');
var response = {
"statusCode": statusCode,
"statusMessage": "Internal server error",
Expand Down
4 changes: 2 additions & 2 deletions lib/src/extract_path_pieces.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ bool isValidHttpPathPattern(String pattern) {
r'^(?:'
r'/'
r'|/(?:[\p{L}\p{N}_-]+(?:/[\p{L}\p{N}_-]+)*/?)'
r'|/(?:[\p{L}\p{N}_-]+(?:/[\p{L}\p{N}_-]+)*/)*:(?:[\p{L}\p{N}_]+)(?:/|$)'
r'|/(?:[\p{L}\p{N}_-]+/)*(?::[\p{L}\p{N}_]+)(?:/[\p{L}\p{N}_-]+)*(?:/(?:[\p{L}\p{N}_-]+/)*(?::[\p{L}\p{N}_]+)(?:/[\p{L}\p{N}_-]+)*)*/?'
r'|/[\p{L}\p{N}_-]+/:[^/]+/\*\*'
r'|/[\p{L}\p{N}_-]+/\*\*'
r'|/[\p{L}\p{N}_-]+/\*'
r'|'
r'| '
r'|\*'
r')$',
unicode: true,
Expand Down
6 changes: 3 additions & 3 deletions lib/src/h4.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class H4 {

for (var key in routeStack.keys) {
if (!key.startsWith('/')) {
logger.warning(
logger.severe(
'Invalid base path! - found $key - change the base path to /$key');
}

Expand Down Expand Up @@ -264,8 +264,8 @@ NotFoundHandler return404(HttpRequest request) {
return defineEventHandler(
(event) {
event.statusCode = 404;
setResponseHeader(event, HttpHeaders.contentTypeHeader,
value: 'application/json');
setResponseHeader(event,
header: HttpHeaders.contentTypeHeader, value: 'application/json');
return {
"statusCode": 404,
"statusMessage": "Not found",
Expand Down
1 change: 0 additions & 1 deletion lib/src/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ class H4Router {
var result = routes.search(pathChunks);

result ??= routes.matchParamRoute(pathChunks);

result ??= routes.matchWildCardRoute(pathChunks);

return result;
Expand Down
99 changes: 62 additions & 37 deletions lib/src/trie.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,60 +73,60 @@ class Trie {

for (String pathPiece in pathPieces) {
int index = pathPieces.indexOf(pathPiece);

if (currNode?.children[pathPiece] == null) {
currNode?.children.forEach((key, value) {
if ((key.startsWith(":") || key.startsWith("*")) && value.isLeaf) {
// Do not behave like a wildcard. Only match if the param route is an exact match.
if (pathPieces.lastOrNull == pathPiece) {
if (index == pathPieces.length - 1) {
laHandler = value.handlers;
}
} else {
// Handle weird edge case where a handler with id as a leaf is defined in route trie
var result = deepTraverse(value.children);

if (result["leaf"] == pathPieces.lastOrNull) {
laHandler = result["handlers"];
}
if (index == pathPieces.length - 1) {
laHandler = value.handlers;
}
}

// Multiple Params
if (key.startsWith(":") && !value.isLeaf) {
var result = deepTraverse(value.children);
var maps = deepTraverse(value.children);
var result = maps["result"];
var prev = maps["prev"];

if (result["leaf"] == pathPieces.lastOrNull) {
laHandler = result["handlers"];
if (result?["leaf"] == pathPieces.lastOrNull) {
laHandler = result?["handlers"];
}

if (result?["leaf"] != null) {
if (result!["leaf"].startsWith(":")) {
if (pathPieces[pathPieces.length - 2] == prev?["key"]) {
laHandler = result["handlers"];
}
}
}
}
});
}
if (laHandler != null) {
break;
}
currNode = currNode?.children[pathPiece];
}
return laHandler;
}

Map<String, String> getParams(pathPieces) {
Map<String, String> params = {};
Map<String, String> getParams(List<String> pathPieces) {
Map<String, dynamic> params = {};
TrieNode? currNode = root;
for (String pathPiece in pathPieces) {
if (currNode?.children[pathPiece] == null) {
currNode?.children.forEach((key, value) {
if (key.startsWith(":")) {
params[key.replaceAll(":", "")] = pathPiece;
}

if (key.startsWith("*")) {
params[key.replaceAll("*", "_")] = pathPiece;
}

if (key.startsWith("**")) {
params[key.replaceAll("**", "_")] = pathPiece;
}
});
params = traverseTrieForSpecialChunks(currNode.children);
Map<String, String> theprms = {};

params.forEach((key, value) {
if (value["leaf"] == true) {
theprms[key] = pathPieces.last;
} else {
List<String> nw = value["prev"].split("/");
var placeholderChunks = nw..removeWhere((item) => item.isEmpty);
theprms.addEntries(
matchPlaceholders(placeholderChunks, pathPieces).entries);
}
currNode = currNode?.children[pathPiece];
}
return params;
});
return theprms;
}

matchWildCardRoute(List<String> pathPieces) {
Expand All @@ -144,8 +144,10 @@ class Trie {
if (key.startsWith("**") && value.isLeaf) {
laHandler = value.handlers;
} else {
var result = deepTraverse(value.children);
laHandler = result["handlers"];
var result = deepTraverse(value.children)["result"];
if (result?["leaf"] == '**') {
laHandler = result?["handlers"];
}
}
});
}
Expand All @@ -154,3 +156,26 @@ class Trie {
return laHandler;
}
}

Map<String, String> matchPlaceholders(
List<String> placeholder, List<String> realString) {
Map<String, String> replacements = {};

// Iterate only up to the length of the placeholder list
for (int i = 0; i < placeholder.length; i++) {
// Check if we're still within the bounds of the realString
if (i < realString.length) {
if (placeholder[i].startsWith(':')) {
replacements[placeholder[i].replaceFirst(':', '')] = realString[i];
} else if (placeholder[i] != realString[i]) {
// Non-placeholder elements must match exactly
return {};
}
} else {
// realString is shorter than placeholder
return {};
}
}

return replacements;
}
Loading
Loading