Skip to content

Commit 134fc92

Browse files
committed
Refactor to make it possible to expose into JS properly.
1 parent 9ea8c8a commit 134fc92

File tree

19 files changed

+746
-265
lines changed

19 files changed

+746
-265
lines changed

__test__/handler.spec.mjs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import test from 'ava'
33
import { Php, Request } from '../index.js'
44

55
import { MockRoot } from './util.mjs'
6+
import { Rewriter } from '../index.js'
67

78
test('Support input/output streams', async (t) => {
89
const mockroot = await MockRoot.from({
@@ -167,3 +168,42 @@ test('Allow receiving true errors', async (t) => {
167168
message: /^Script not found: .*\/index\.php$/
168169
}, 'should throw error')
169170
})
171+
172+
test('Accept rewriter', async (t) => {
173+
const mockroot = await MockRoot.from({
174+
'index.php': '<?php echo "Hello, World!"; ?>'
175+
})
176+
t.teardown(() => mockroot.clean())
177+
178+
const rewrite = new Rewriter([
179+
{
180+
conditions: [
181+
{
182+
type: 'path',
183+
args: ['^/rewrite_me$']
184+
}
185+
],
186+
rewriters: [
187+
{
188+
type: 'path',
189+
args: ['^/rewrite_me$', '/index.php']
190+
}
191+
]
192+
}
193+
])
194+
195+
const php = new Php({
196+
argv: process.argv,
197+
docroot: mockroot.path,
198+
throwRequestErrors: true,
199+
rewrite
200+
})
201+
202+
const req = new Request({
203+
url: 'http://example.com/rewrite_me'
204+
})
205+
206+
const res = await php.handleRequest(req)
207+
t.is(res.status, 200)
208+
t.is(res.body.toString('utf8'), 'Hello, World!')
209+
})
Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
use super::{Condition, Request};
22

3-
/// Support plain functions as conditions
43
impl<F> Condition for F
54
where
65
F: Fn(&Request) -> bool + Sync + Send,
76
{
7+
/// Matches if calling the Fn(&Request) with the given request returns true
8+
///
9+
/// # Examples
10+
///
11+
/// ```
12+
/// # use lang_handler::{Request, rewrite::Condition};
13+
/// let condition = |request: &Request| {
14+
/// request.url().path().contains("/foo")
15+
/// };
16+
///
17+
/// let request = Request::builder()
18+
/// .url("http://example.com/index.php")
19+
/// .build()
20+
/// .expect("request should build");
21+
///
22+
/// assert_eq!(condition.matches(&request), false);
23+
/// ```
824
fn matches(&self, request: &Request) -> bool {
925
self(request)
1026
}
1127
}
12-
13-
#[cfg(test)]
14-
mod test {
15-
use super::*;
16-
17-
#[test]
18-
fn test_closure_condition() {
19-
let condition = |_: &Request| true;
20-
21-
let request = Request::builder()
22-
.url("http://example.com/index.php")
23-
.build()
24-
.expect("request should build");
25-
26-
assert!(condition.matches(&request));
27-
}
28-
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
use std::path::PathBuf;
2+
3+
use super::Condition;
4+
use super::Request;
5+
6+
/// Match if request path exists
7+
#[derive(Clone, Debug)]
8+
pub struct ExistenceCondition(PathBuf);
9+
10+
impl ExistenceCondition {
11+
/// Construct an ExistenceCondition to check within a given base directory.
12+
///
13+
/// # Examples
14+
///
15+
/// ```
16+
/// # use lang_handler::rewrite::{Condition, ExistenceCondition};
17+
/// # use lang_handler::Request;
18+
/// let condition = ExistenceCondition::new("/foo/bar");
19+
/// ```
20+
pub fn new<P>(base: P) -> Self
21+
where
22+
P: Into<PathBuf>,
23+
{
24+
Self(base.into())
25+
}
26+
}
27+
28+
impl Condition for ExistenceCondition {
29+
/// A NonExistenceCondition matches a request if the path segment of the
30+
/// request url does not exist in the provided base directory.
31+
///
32+
/// # Examples
33+
///
34+
/// ```
35+
/// # use lang_handler::rewrite::{Condition, ExistenceCondition};
36+
/// # use lang_handler::Request;
37+
/// let condition = ExistenceCondition::new("/foo/bar");
38+
///
39+
/// let request = Request::builder()
40+
/// .url("http://example.com/index.php")
41+
/// .build()
42+
/// .expect("should build request");
43+
///
44+
/// assert_eq!(condition.matches(&request), false);
45+
/// ```
46+
fn matches(&self, request: &Request) -> bool {
47+
self
48+
.0
49+
.join(request.url().path().strip_prefix("/").unwrap())
50+
.canonicalize()
51+
.is_ok()
52+
}
53+
}
54+
55+
/// Match if request path does not exist
56+
#[derive(Clone, Debug, Default)]
57+
pub struct NonExistenceCondition(PathBuf);
58+
59+
impl NonExistenceCondition {
60+
/// Construct a NonExistenceCondition to check within a given base directory.
61+
///
62+
/// # Examples
63+
///
64+
/// ```
65+
/// # use lang_handler::rewrite::{Condition, NonExistenceCondition};
66+
/// # use lang_handler::Request;
67+
/// let condition = NonExistenceCondition::new("/foo/bar");
68+
/// ```
69+
pub fn new<P>(base: P) -> Self
70+
where
71+
P: Into<PathBuf>,
72+
{
73+
Self(base.into())
74+
}
75+
}
76+
77+
impl Condition for NonExistenceCondition {
78+
/// A NonExistenceCondition matches a request if the path segment of the
79+
/// request url does not exist in the provided base directory.
80+
///
81+
/// # Examples
82+
///
83+
/// ```
84+
/// # use lang_handler::rewrite::{Condition, NonExistenceCondition};
85+
/// # use lang_handler::Request;
86+
/// let condition = NonExistenceCondition::new("/foo/bar");
87+
///
88+
/// let request = Request::builder()
89+
/// .url("http://example.com/index.php")
90+
/// .build()
91+
/// .expect("should build request");
92+
///
93+
/// assert!(condition.matches(&request));
94+
/// ```
95+
fn matches(&self, request: &Request) -> bool {
96+
self
97+
.0
98+
.join(request.url().path().strip_prefix("/").unwrap())
99+
.canonicalize()
100+
.is_err()
101+
}
102+
}
103+
104+
#[cfg(test)]
105+
mod test {
106+
use super::*;
107+
108+
use std::{
109+
env::current_dir,
110+
fs::File,
111+
io::Write,
112+
path::{Path, PathBuf},
113+
};
114+
115+
struct TempFile(PathBuf);
116+
117+
impl TempFile {
118+
fn new<P: AsRef<Path>, S: Into<String>>(path: P, contents: S) -> Self {
119+
let mut file = File::create(path.as_ref()).unwrap();
120+
file.write_all(contents.into().as_bytes()).unwrap();
121+
Self(path.as_ref().to_owned())
122+
}
123+
}
124+
125+
impl Drop for TempFile {
126+
fn drop(&mut self) {
127+
std::fs::remove_file(&self.0).unwrap();
128+
}
129+
}
130+
131+
#[test]
132+
fn test_existence_condition() {
133+
let _temp = TempFile::new("exists.php", "<?php echo \"Hello, world!\"; ?>");
134+
135+
let cwd = current_dir().unwrap();
136+
let condition = ExistenceCondition::new(cwd);
137+
138+
let request = Request::builder()
139+
.url("http://example.com/exists.php")
140+
.build()
141+
.expect("request should build");
142+
143+
assert!(condition.matches(&request));
144+
}
145+
146+
#[test]
147+
fn test_non_existence_condition() {
148+
let cwd = current_dir().unwrap();
149+
let condition = NonExistenceCondition::new(cwd);
150+
151+
let request = Request::builder()
152+
.url("http://example.com/does_not_exist.php")
153+
.build()
154+
.expect("request should build");
155+
156+
assert!(condition.matches(&request));
157+
}
158+
}

crates/lang_handler/src/rewrite/condition/group.rs

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,54 @@ use super::{Condition, Request};
22

33
/// This provides logical grouping of conditions using either AND or OR
44
/// combination behaviours.
5-
pub enum ConditionGroup<'a> {
6-
Or(&'a dyn Condition, &'a dyn Condition),
7-
And(&'a dyn Condition, &'a dyn Condition),
5+
pub enum ConditionGroup<A, B>
6+
where
7+
A: Condition + ?Sized,
8+
B: Condition + ?Sized,
9+
{
10+
Or(Box<A>, Box<B>),
11+
And(Box<A>, Box<B>),
812
}
913

10-
impl Condition for ConditionGroup<'_> {
14+
impl<A, B> ConditionGroup<A, B>
15+
where
16+
A: Condition + ?Sized,
17+
B: Condition + ?Sized,
18+
{
19+
pub fn and(a: Box<A>, b: Box<B>) -> Box<Self> {
20+
Box::new(ConditionGroup::And(a, b))
21+
}
22+
23+
pub fn or(a: Box<A>, b: Box<B>) -> Box<Self> {
24+
Box::new(ConditionGroup::Or(a, b))
25+
}
26+
}
27+
28+
impl<A, B> Condition for ConditionGroup<A, B>
29+
where
30+
A: Condition + ?Sized,
31+
B: Condition + ?Sized,
32+
{
1133
fn matches(&self, request: &Request) -> bool {
1234
match self {
1335
ConditionGroup::Or(a, b) => a.matches(request) || b.matches(request),
14-
ConditionGroup::And(a, b) => a.matches(request) && b.matches(request)
36+
ConditionGroup::And(a, b) => a.matches(request) && b.matches(request),
1537
}
1638
}
1739
}
1840

1941
#[cfg(test)]
2042
mod test {
2143
use super::*;
22-
use crate::rewrite::{HeaderCondition, PathCondition};
44+
use crate::rewrite::{ConditionExt, HeaderCondition, PathCondition};
2345

2446
#[test]
2547
fn test_condition_group_and() {
26-
let header = HeaderCondition::new("TEST", "^foo$")
27-
.expect("should be valid regex");
48+
let header = HeaderCondition::new("TEST", "^foo$").expect("should be valid regex");
2849

29-
let path = PathCondition::new("^/index\\.php$")
30-
.expect("should be valid regex");
50+
let path = PathCondition::new("^/index\\.php$").expect("should be valid regex");
3151

32-
let header_and_path = header.and(&path);
52+
let header_and_path = header.and(path);
3353

3454
// Check it matches when all conditions match
3555
let request = Request::builder()
@@ -59,13 +79,11 @@ mod test {
5979

6080
#[test]
6181
fn test_condition_group_or() {
62-
let header = HeaderCondition::new("TEST", "^foo$")
63-
.expect("should be valid regex");
82+
let header = HeaderCondition::new("TEST", "^foo$").expect("should be valid regex");
6483

65-
let path = PathCondition::new("^/index\\.php$")
66-
.expect("should be valid regex");
84+
let path = PathCondition::new("^/index\\.php$").expect("should be valid regex");
6785

68-
let header_or_path = header.or(&path);
86+
let header_or_path = header.or(path);
6987

7088
// Check it matches when either condition matches
7189
let request = Request::builder()
@@ -83,13 +101,13 @@ mod test {
83101
.build()
84102
.expect("request should build");
85103

86-
assert!(!header_or_path.matches(&only_header));
104+
assert!(header_or_path.matches(&only_header));
87105

88106
let only_url = Request::builder()
89107
.url("http://example.com/index.php")
90108
.build()
91109
.expect("request should build");
92110

93-
assert!(!header_or_path.matches(&only_url));
111+
assert!(header_or_path.matches(&only_url));
94112
}
95113
}

crates/lang_handler/src/rewrite/condition/header.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ pub struct HeaderCondition {
1515
impl HeaderCondition {
1616
/// Construct a new HeaderCondition matching the given header name and Regex
1717
/// pattern.
18-
pub fn new<S, R>(name: S, pattern: R) -> Result<Self, Error>
18+
pub fn new<S, R>(name: S, pattern: R) -> Result<Box<Self>, Error>
1919
where
2020
S: Into<String>,
2121
R: TryInto<Regex>,
2222
Error: From<<R as TryInto<Regex>>::Error>,
2323
{
2424
let name = name.into();
2525
let pattern = pattern.try_into()?;
26-
Ok(Self { name, pattern })
26+
Ok(Box::new(Self { name, pattern }))
2727
}
2828
}
2929

@@ -45,8 +45,7 @@ mod test {
4545

4646
#[test]
4747
fn test_header_condition() {
48-
let condition = HeaderCondition::new("TEST", "^foo$")
49-
.expect("regex should be valid");
48+
let condition = HeaderCondition::new("TEST", "^foo$").expect("regex should be valid");
5049

5150
let request = Request::builder()
5251
.url("http://example.com/")

0 commit comments

Comments
 (0)