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

runtimes/js: Add endpoint tags #1726

Merged
merged 4 commits into from
Jan 20, 2025
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
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)))
fredr marked this conversation as resolved.
Show resolved Hide resolved
);
});

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
Loading