Skip to content

Commit

Permalink
runtimes/js: Add endpoint tags (#1726)
Browse files Browse the repository at this point in the history
  • Loading branch information
fredr authored Jan 20, 2025
1 parent b32b0a6 commit 019677f
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 41 deletions.
14 changes: 11 additions & 3 deletions docs/ts/develop/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,15 @@ export default new Service("myService", {

## Targeting APIs

The target option specifies which endpoints within the service the middleware should run on. If not set, the middleware will run for all endpoints by default.
The `target` option specifies which endpoints within the service the middleware should run on. If not set, the middleware will run for all endpoints by default.

For better performance, use the `target` option instead of filtering within the middleware function. This allows the applicable middleware to be determined per endpoint during startup, reducing runtime overhead.

The following options are available for targeting endpoints:

- `tags`: A list of tags evaluated with `OR`, meaning the middleware applies to an endpoint if the endpoint has at least one of these tags.
- `expose`: A boolean indicating whether the middleware should be applied to endpoints that are exposed or not exposed.
- `auth`: A boolean indicating whether the middleware should be applied to endpoints that require authentication or not.
- `isRaw`: A boolean indicating whether the middleware should be applied to raw endpoints.
- `isStream`: A boolean indicating whether the middleware should be applied to stream endpoints.

For better performance, use the `target` option instead of filtering within the middleware function.
This enables calculating applicable middleware per endpoint during startup, reducing runtime overhead.
12 changes: 12 additions & 0 deletions runtimes/js/encore.dev/api/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ export interface APIOptions {
* If set to `null`, the body size is unlimited.
**/
bodyLimit?: number | null;

/**
* Tags to filter endpoints when generating clients and in middlewares.
*/
tags?: string[];
}

export interface StreamOptions {
Expand Down Expand Up @@ -292,6 +297,13 @@ export interface MiddlewareOptions {
* If set, only run middleware on endpoints that are stream endpoints.
*/
isStream?: boolean;

/**
* If set, only run middleware on endpoints that have specific tags.
* These tags are evaluated with OR, meaning the middleware applies to an
* API if the API has at least one of those tags.
*/
tags?: string[];
};
}

Expand Down
50 changes: 14 additions & 36 deletions runtimes/js/encore.dev/internal/appinit/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ interface EndpointOptions {
auth: boolean;
isRaw: boolean;
isStream: boolean;
tags: string[];
}

export interface InternalHandlerResponse {
Expand Down Expand Up @@ -91,42 +92,19 @@ function calculateMiddlewareChain(
endpointOptions: EndpointOptions,
ms: Middleware[]
): Middleware[] {
let middlewares = [];

for (const m of ms) {
if (m.options === undefined || m.options.target === undefined) {
middlewares.push(m);
} else {
const target = m.options.target;
// check if options are set and if they match the endpoint options
if (target.auth !== undefined && target.auth !== endpointOptions.auth) {
continue;
}

if (
target.expose !== undefined &&
target.expose !== endpointOptions.expose
) {
continue;
}

if (
target.isRaw !== undefined &&
target.isRaw !== endpointOptions.isRaw
) {
continue;
}

if (
target.isStream !== undefined &&
target.isStream !== endpointOptions.isStream
) {
continue;
}

middlewares.push(m);
}
}
const middlewares = ms.filter((m) => {
const target = m.options?.target;
if (!target) return true;
const { auth, expose, isRaw, isStream, tags } = target;
return (
(auth === undefined || auth === endpointOptions.auth) &&
(expose === undefined || expose === endpointOptions.expose) &&
(isRaw === undefined || isRaw === endpointOptions.isRaw) &&
(isStream === undefined || isStream === endpointOptions.isStream) &&
(tags === undefined ||
tags.some((tag) => endpointOptions.tags.includes(tag)))
);
});

return middlewares;
}
Expand Down
23 changes: 23 additions & 0 deletions tsparser/litparser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,29 @@ impl LitParser for std::time::Duration {
}
}

impl<T> LitParser for Vec<T>
where
T: LitParser,
{
fn parse_lit(input: &swc_ecma_ast::Expr) -> ParseResult<Self> {
match input {
ast::Expr::Array(array) => {
let mut vec = Vec::new();
for elem in &array.elems {
if let Some(expr) = elem {
let parsed_elem = T::parse_lit(&expr.expr)?;
vec.push(parsed_elem);
} else {
return Err(array.span.parse_err("expected array element"));
}
}
Ok(vec)
}
_ => Err(input.parse_err("expected array literal")),
}
}
}

/// Represents a local, relative path (without ".." or a root).
#[derive(Debug, Clone)]
pub struct LocalRelPath {
Expand Down
3 changes: 3 additions & 0 deletions tsparser/src/builder/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ impl Builder<'_> {
"auth": rpc.require_auth,
"isRaw": rpc.raw,
"isStream": rpc.streaming_request || rpc.streaming_response,
"tags": rpc.tags,
}),
}));
}
Expand Down Expand Up @@ -280,6 +281,7 @@ impl Builder<'_> {
"auth": rpc.require_auth,
"isRaw": rpc.raw,
"isStream": rpc.streaming_request || rpc.streaming_response,
"tags": rpc.tags,
}),
}));
}
Expand Down Expand Up @@ -430,6 +432,7 @@ impl Builder<'_> {
"auth": rpc.require_auth,
"isRaw": rpc.raw,
"isStream": rpc.streaming_request || rpc.streaming_response,
"tags": rpc.tags,
}),
}));
}
Expand Down
15 changes: 13 additions & 2 deletions tsparser/src/legacymeta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::rc::Rc;

use swc_common::errors::HANDLER;

use crate::encore::parser::meta::v1;
use crate::encore::parser::meta::v1::{self, selector, Selector};
use crate::legacymeta::schema::{loc_from_range, SchemaBuilder};
use crate::parser::parser::{ParseContext, ParseResult, Service};
use crate::parser::resourceparser::bind::{Bind, BindKind};
Expand Down Expand Up @@ -164,6 +164,17 @@ impl MetaBuilder<'_> {
})
.transpose()?;

let tags = ep
.tags
.as_ref()
.unwrap_or(&vec![])
.iter()
.map(|tag| Selector {
r#type: selector::Type::Tag.into(),
value: tag.clone(),
})
.collect();

let rpc = v1::Rpc {
name: ep.name.clone(),
doc: ep.doc.clone(),
Expand All @@ -179,7 +190,7 @@ impl MetaBuilder<'_> {
} as i32,
path: Some(ep.encoding.path.to_meta()),
http_methods: ep.encoding.methods.to_vec(),
tags: vec![],
tags,
sensitive: false,
loc: Some(loc_from_range(self.app_root, &self.pc.file_set, ep.range)?),
allow_unauthenticated: !ep.require_auth,
Expand Down
3 changes: 3 additions & 0 deletions tsparser/src/parser/resources/apis/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub struct Endpoint {
pub expose: bool,
pub raw: bool,
pub require_auth: bool,
pub tags: Option<Vec<String>>,

/// Body limit in bytes.
/// None means no limit.
Expand Down Expand Up @@ -374,6 +375,7 @@ pub const ENDPOINT_PARSER: ResourceParser = ResourceParser {
static_assets,
body_limit,
encoding,
tags: cfg.tags,
}));

pass.add_resource(resource.clone());
Expand Down Expand Up @@ -468,6 +470,7 @@ struct EndpointConfig {
expose: Option<bool>,
auth: Option<bool>,
bodyLimit: Option<Nullable<u64>>,
tags: Option<Vec<String>>,

// For static assets.
dir: Option<Sp<LocalRelPath>>,
Expand Down
2 changes: 2 additions & 0 deletions tsparser/src/parser/usageparser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ export const Bar = 5;
streaming_request: false,
streaming_response: false,
static_assets: None,
tags: None,
}));

let bar_binds = vec![Lrc::new(Bind {
Expand Down Expand Up @@ -653,6 +654,7 @@ export const Bar = 5;
streaming_request: false,
streaming_response: false,
static_assets: None,
tags: None,
}));
let bar_binds = vec![Lrc::new(Bind {
kind: BindKind::Create,
Expand Down

0 comments on commit 019677f

Please sign in to comment.