Skip to content

Commit

Permalink
fix(rss): make title optional if description is provided (#9610)
Browse files Browse the repository at this point in the history
* fix(rss): make title optional if description is provided

* feat(rss): simplify schema

* fix(rss): update tests to match new behavior

* Update packages/astro-rss/test/pagesGlobToRssItems.test.js

Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>

* Update packages/astro-rss/test/pagesGlobToRssItems.test.js

Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>

* feat: make link and pubDate optional

* feat: improve item normalization

* Update shy-spoons-sort.md

* Fix test fail

* Update .changeset/shy-spoons-sort.md

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

---------

Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>
Co-authored-by: bluwy <bjornlu.dev@gmail.com>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
  • Loading branch information
4 people committed Jan 6, 2024
1 parent edc87ab commit 24663c9
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/shy-spoons-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@astrojs/rss": patch
---

Fixes the RSS schema to make the `title` optional if the description is already provided. It also makes `pubDate` and `link` optional, as specified in the RSS specification.
38 changes: 19 additions & 19 deletions packages/astro-rss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ export type RSSOptions = {

export type RSSFeedItem = {
/** Link to item */
link: string;
link: z.infer<typeof rssSchema>['link'];
/** Full content of the item. Should be valid HTML */
content?: string | undefined;
content?: z.infer<typeof rssSchema>['content'];
/** Title of item */
title: z.infer<typeof rssSchema>['title'];
/** Publication date of item */
Expand All @@ -55,19 +55,18 @@ export type RSSFeedItem = {
enclosure?: z.infer<typeof rssSchema>['enclosure'];
};

type ValidatedRSSFeedItem = z.infer<typeof rssFeedItemValidator>;
type ValidatedRSSFeedItem = z.infer<typeof rssSchema>;
type ValidatedRSSOptions = z.infer<typeof rssOptionsValidator>;
type GlobResult = z.infer<typeof globResultValidator>;

const rssFeedItemValidator = rssSchema.extend({ link: z.string(), content: z.string().optional() });
const globResultValidator = z.record(z.function().returns(z.promise(z.any())));

const rssOptionsValidator = z.object({
title: z.string(),
description: z.string(),
site: z.preprocess((url) => (url instanceof URL ? url.href : url), z.string().url()),
items: z
.array(rssFeedItemValidator)
.array(rssSchema)
.or(globResultValidator)
.transform((items) => {
if (!Array.isArray(items)) {
Expand Down Expand Up @@ -117,7 +116,7 @@ async function validateRssOptions(rssOptions: RSSOptions) {
if (path === 'items' && code === 'invalid_union') {
return [
message,
`The \`items\` property requires properly typed \`title\`, \`pubDate\`, and \`link\` keys.`,
`The \`items\` property requires at least the \`title\` or \`description\` key. They must be properly typed, as well as \`pubDate\` and \`link\` keys if provided.`,
`Check your collection's schema, and visit https://docs.astro.build/en/guides/rss/#generating-items for more info.`,
].join('\n');
}
Expand All @@ -138,10 +137,7 @@ export function pagesGlobToRssItems(items: GlobResult): Promise<ValidatedRSSFeed
`[RSS] You can only glob entries within 'src/pages/' when passing import.meta.glob() directly. Consider mapping the result to an array of RSSFeedItems. See the RSS docs for usage examples: https://docs.astro.build/en/guides/rss/#2-list-of-rss-feed-objects`
);
}
const parsedResult = rssFeedItemValidator.safeParse(
{ ...frontmatter, link: url },
{ errorMap }
);
const parsedResult = rssSchema.safeParse({ ...frontmatter, link: url }, { errorMap });

if (parsedResult.success) {
return parsedResult.data;
Expand Down Expand Up @@ -210,15 +206,19 @@ async function generateRSS(rssOptions: ValidatedRSSOptions): Promise<string> {
);
// items
root.rss.channel.item = items.map((result) => {
// If the item's link is already a valid URL, don't mess with it.
const itemLink = isValidURL(result.link)
? result.link
: createCanonicalURL(result.link, rssOptions.trailingSlash, site).href;
const item: any = {
title: result.title,
link: itemLink,
guid: { '#text': itemLink, '@_isPermaLink': 'true' },
};
const item: Record<string, unknown> = {};

if (result.title) {
item.title = result.title;
}
if (typeof result.link === 'string') {
// If the item's link is already a valid URL, don't mess with it.
const itemLink = isValidURL(result.link)
? result.link
: createCanonicalURL(result.link, rssOptions.trailingSlash, site).href;
item.link = itemLink;
item.guid = { '#text': itemLink, '@_isPermaLink': 'true' };
}
if (result.description) {
item.description = result.description;
}
Expand Down
26 changes: 21 additions & 5 deletions packages/astro-rss/src/schema.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { z } from 'astro/zod';

export const rssSchema = z.object({
title: z.string(),
const sharedSchema = z.object({
pubDate: z
.union([z.string(), z.number(), z.date()])
.transform((value) => new Date(value))
.refine((value) => !isNaN(value.getTime())),
description: z.string().optional(),
.optional()
.transform((value) => (value === undefined ? value : new Date(value)))
.refine((value) => (value === undefined ? value : !isNaN(value.getTime()))),
customData: z.string().optional(),
categories: z.array(z.string()).optional(),
author: z.string().optional(),
Expand All @@ -19,4 +18,21 @@ export const rssSchema = z.object({
type: z.string(),
})
.optional(),
link: z.string().optional(),
content: z.string().optional(),
});

export const rssSchema = z.union([
z
.object({
title: z.string(),
description: z.string().optional(),
})
.merge(sharedSchema),
z
.object({
title: z.string().optional(),
description: z.string(),
})
.merge(sharedSchema),
]);
38 changes: 36 additions & 2 deletions packages/astro-rss/test/pagesGlobToRssItems.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('pagesGlobToRssItems', () => {
return chai.expect(pagesGlobToRssItems(globResult)).to.be.rejected;
});

it('should fail on missing "title" key', () => {
it('should fail on missing "title" key and "description"', () => {
const globResult = {
'./posts/php.md': () =>
new Promise((resolve) =>
Expand All @@ -75,11 +75,45 @@ describe('pagesGlobToRssItems', () => {
frontmatter: {
title: undefined,
pubDate: phpFeedItem.pubDate,
description: phpFeedItem.description,
description: undefined,
},
})
),
};
return chai.expect(pagesGlobToRssItems(globResult)).to.be.rejected;
});

it('should not fail on missing "title" key if "description" is present', () => {
const globResult = {
'./posts/php.md': () =>
new Promise((resolve) =>
resolve({
url: phpFeedItem.link,
frontmatter: {
title: undefined,
pubDate: phpFeedItem.pubDate,
description: phpFeedItem.description,
},
})
),
};
return chai.expect(pagesGlobToRssItems(globResult)).to.not.be.rejected;
});

it('should fail on missing "description" key if "title" is present', () => {
const globResult = {
'./posts/php.md': () =>
new Promise((resolve) =>
resolve({
url: phpFeedItem.link,
frontmatter: {
title: phpFeedItem.title,
pubDate: phpFeedItem.pubDate,
description: undefined,
},
})
),
};
return chai.expect(pagesGlobToRssItems(globResult)).to.not.be.rejected;
});
});

0 comments on commit 24663c9

Please sign in to comment.