Skip to content

build コマンドを関数型パイプラインアーキテクチャにリファクタリング#48

Open
zztkm wants to merge 2 commits intomainfrom
refactor/build-functional-pipeline
Open

build コマンドを関数型パイプラインアーキテクチャにリファクタリング#48
zztkm wants to merge 2 commits intomainfrom
refactor/build-functional-pipeline

Conversation

@zztkm
Copy link
Member

@zztkm zztkm commented Jan 31, 2026

Summary

  • build コマンドの内部実装を関数型パイプラインアーキテクチャにリファクタリング
  • データの流れを明確化し、副作用(ファイル I/O)を分離

新しいデータ構造

  • ParsedDocument - パース済み Markdown(frontmatter と HTML コンテンツを保持)
  • RenderedPage - レンダリング済みページ(出力パス、HTML、メタデータを保持)
  • OutputFile - 出力ファイル(パスとコンテンツを保持)

パイプライン構成

Phase 1: 収集 & 変換(純粋関数)
  collect_markdown_sources() → parse_document() → render_page() → generate_tag_outputs()

Phase 2: 出力(副作用)
  create_dist_dir() → copy_static_files() → write_all_outputs()

Test plan

  • cargo build でコンパイル確認
  • cargo test でテスト実行
  • example_sitevss build を実行し、生成結果を確認
  • sitevss build を実行し、生成結果を確認

🤖 Generated with Claude Code

- 新しいデータ構造を追加 (ParsedDocument, RenderedPage, OutputFile)
- 純粋関数でデータ変換を行うように変更
- 副作用(ファイル I/O)を Phase 2 に分離
- 不要になった関数を削除

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @zztkm, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

このプルリクエストは、静的サイトジェネレータの build コマンドのコアロジックを、より構造化された関数型パイプラインモデルに移行させることを目的としています。この変更により、コンテンツの収集、パース、レンダリング、そして最終的な出力という一連のプロセスが、明確に定義されたステージとデータフローを持つようになります。これにより、コードの可読性と拡張性が向上し、将来的な機能追加や変更が容易になります。

Highlights

  • ビルドコマンドのアーキテクチャリファクタリング: build コマンドの内部実装が、関数型パイプラインアーキテクチャに大幅にリファクタリングされました。これにより、データの流れが明確化され、保守性が向上します。
  • 副作用の分離: ファイルI/Oなどの副作用を伴う処理が、純粋な変換処理から分離されました。これにより、各関数の責務が明確になり、テストが容易になります。
  • 新しいデータ構造の導入: パイプラインの各段階でデータを表現するための新しい構造体(ParsedDocumentRenderedPageOutputFile)が導入されました。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an excellent refactoring of the build command to a functional pipeline architecture, significantly improving code clarity and maintainability by separating pure transformations and file I/O. However, a high-severity path traversal vulnerability was identified in the tag page generation logic. Unsanitized tag names from markdown files are used to construct output file paths, which could allow a malicious markdown file to overwrite arbitrary files on the system where the build is performed. It is recommended to sanitize tag names to ensure they only contain safe characters before using them in file paths. Additionally, the review highlighted suggestions to keep phase 1 functions more strictly pure and improvements to reduce unnecessary data clones for better performance.

Comment on lines +500 to +509
let tag_path_str = config.build.tags.url_pattern.replace("{tag}", &tag_name);
let relative_path = tag_path_str.trim_start_matches('/');

let path = if relative_path.ends_with('/') {
Path::new(&config.dist)
.join(relative_path)
.join("index.html")
} else {
Path::new(&config.dist).join(relative_path)
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The tag_name from markdown frontmatter is used to construct the output file path without sanitization. A malicious tag name like ../../evil can lead to a path traversal vulnerability, allowing arbitrary files to be written outside the dist directory during the build process. This is particularly dangerous if the tool is used to build sites from untrusted repositories.

Suggested change
let tag_path_str = config.build.tags.url_pattern.replace("{tag}", &tag_name);
let relative_path = tag_path_str.trim_start_matches('/');
let path = if relative_path.ends_with('/') {
Path::new(&config.dist)
.join(relative_path)
.join("index.html")
} else {
Path::new(&config.dist).join(relative_path)
};
let sanitized_tag = tag_name.replace(|c: char| !c.is_alphanumeric() && c != '-' && c != '_', "_");
let tag_path_str = config.build.tags.url_pattern.replace("{tag}", &sanitized_tag);
let relative_path = tag_path_str.trim_start_matches('/');
let path = if relative_path.ends_with('/') {
Path::new(&config.dist)
.join(relative_path)
.join("index.html")
} else {
Path::new(&config.dist).join(relative_path)
};

fs::write(&output_path, rendered)
.with_context(|| format!("Failed to write output file: {}", output_path.display()))?;

println!("Generated: {}", output_path.display());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

render_page 関数は純粋関数であるべきフェーズ1の一部ですが、副作用を持つ println! が含まれています。アーキテクチャの一貫性を保つため、このコンソール出力は副作用を扱うフェーズ2に移動させることを推奨します。例えば、write_all_outputs 関数内でファイルの書き込みが成功した直後に出力するように変更できます。

Path::new(&config.dist).join(relative_path)
};

println!("Generated tag page: {}", path.display());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

generate_tag_outputs 関数も同様に、純粋関数であるべきフェーズ1の一部ですが、副作用を持つ println! が含まれています。render_page での指摘と同様に、この出力もフェーズ2に移動させ、副作用を1箇所に集約するのが望ましいです。

Comment on lines +616 to +621
write_all_outputs(
rendered_pages.iter().map(|p| OutputFile {
path: p.output_path.clone(),
content: p.html.clone(),
}),
)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ここで rendered_pages.iter() で参照としてイテレートされ、各要素のフィールドがクローンされています。しかし、この処理の後 rendered_pages は使われていないため、.into_iter() を使って所有権をムーブすることで、pathcontent のクローンを避けることができます。

Suggested change
write_all_outputs(
rendered_pages.iter().map(|p| OutputFile {
path: p.output_path.clone(),
content: p.html.clone(),
}),
)?;
write_all_outputs(
rendered_pages.into_iter().map(|p| OutputFile {
path: p.output_path,
content: p.html,
}),
)?;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant