本文へ移動
cccskills
無料GitHub で公開日本語紹介

connector-transform

Apache Iggyコネクターのメッセージにフィールド追加・削除や形式変換などの処理を加える変換機能を設計し、設定連携やテストまで整えます。

原文原文の説明を見る

Add a new transform under `core/connectors/sdk/src/transforms/`. Transforms mutate, filter, or convert messages between source/encode and decode/sink. Use when adding field-level transforms (add/delete/update/filter), format conversions, envelope unwrapping, or similar config-driven message manipulations for Apache Iggy connectors. Use for transform authoring. NOT for runtime internals or sink/source loops.

apache/iggyスター 5,223(GitHub 5,223 + cccskills 0)

実行スコア 未検証

インストール方法を見る

こんなときに便利

  • メッセージにフィールドを追加したいとき
  • 条件に応じてメッセージを除外したいとき
  • データ形式を変換したいとき
  • 入れ子のデータを展開したいとき

日本語での紹介

できること

Apache Iggy Connectors SDKにメッセージ変換処理を追加する手順を案内します。変換処理はシンク側ではデコード後、ソース側ではエンコード前に実行され、設定に応じてフィールドを追加・削除・更新したり、条件に合うメッセージを除外したりできます。形式変換やネストされたデータの展開にも対応する実装例を示します。変換型の登録、設定ファクトリーへの接続、READMEの使用例、正常系・通過・除外・不正設定のテストまで扱います。設定検証や正規表現などの準備は初期化時に行い、メッセージ処理時の無駄な複製を避ける考え方も説明します。

こんなときに便利

コネクター間でメッセージの形を整えたいときや、不要なメッセージを条件で除きたいときに使えます。既存の変換処理と同じ構成で新しい変換機能をSDKに追加したい開発者にも向いています。

使い方の例

  • 「JSONのフィールドを追加する変換処理を作ってください」
  • 「条件に合わないメッセージを除外する変換を追加してください」

注意点

変換処理は同期実行で、初期化後は不変の状態で共有されます。ペイロードの形を変える場合は、接続先への影響を確認します。

この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。

含まれるファイル(1)

  • SKILL.md10.8 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Adding a New Apache Iggy Connector Transform

A transform implements iggy_connector_sdk::transforms::Transform. It runs after decode (for sinks) or before encode (for sources), in a chain configured per-connector. Config-driven, composable.

Universal connector rules live in connectors-overview. This skill covers transform authoring only.

Contents

STOP and ask the user before

  • Adding a new transform that mutates payload SHAPE (renaming PK fields, dropping schema-required columns) - breaks downstream sinks silently.
  • Introducing non-determinism (system time, RNG without seed) - breaks tests + replay.
  • Changing the Transform trait signature (sync vs async, return type) - cascades to every transform.
  • Making transforms hold mutable runtime state - they're constructed once and Arc-cloned across tasks. mutation requires interior locking and is rarely what you actually want.

File map

sdk/src/transforms/
├── mod.rs                 Transform trait, TransformType enum, from_config() factory,
│                          FieldValue + ComputedValue helpers
├── add_fields.rs          AddFields - append static or computed fields to JSON
├── delete_fields.rs       DeleteFields - remove fields by key
├── update_fields.rs       UpdateFields - conditional field replacement
├── filter_fields.rs       FilterFields - keep/drop messages by key/value pattern
├── unwrap_envelope.rs     UnwrapEnvelope - promote nested field to top-level payload
├── proto_convert.rs       ProtoConvert - bidirectional Proto ⇄ Json/Text/Raw/etc.
├── flatbuffer_convert.rs  FlatBufferConvert
├── avro_convert.rs        AvroConvert
└── json/                  Shared JSON helpers (path traversal, ops)

The trait

// sdk/src/transforms/mod.rs - Transform
pub trait Transform: Send + Sync {
    fn r#type(&self) -> TransformType;
    fn transform(
        &self,
        metadata: &TopicMetadata,
        message: DecodedMessage,
    ) -> Result<Option<DecodedMessage>, Error>;
}

Semantics:

  • &self - transforms are immutable post-construction. Internal state (compiled regex, parsed paths, schema descriptors) is computed in new() and stored as plain struct fields. No locks needed - the whole instance is shared via Arc<dyn Transform>.
  • Takes message by value - the transform owns the DecodedMessage and may mutate freely without cloning.
  • Returns Ok(Some(msg)) to pass through (possibly modified), Ok(None) to filter (drop silently), Err(e) for actual failures.
  • &TopicMetadata is for routing-aware logic. Most transforms ignore it.

Steps to add a new transform

1. Create the file

sdk/src/transforms/my_transform.rs. Apache 2.0 license header. Code reads top to bottom - config struct, then transform struct, then impl, then helpers.

/* Apache 2.0 header */

use crate::transforms::{Transform, TransformType};
use crate::{DecodedMessage, Error, Payload, TopicMetadata};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MyTransformConfig {
    pub key: String,
    pub option: Option<bool>,
}

pub struct MyTransform {
    config: MyTransformConfig,
}

impl MyTransform {
    pub fn new(config: MyTransformConfig) -> Result<Self, Error> {
        if config.key.is_empty() {
            return Err(Error::InvalidConfigValue(
                "my_transform.key must not be empty".into(),
            ));
        }
        Ok(Self { config })
    }
}

impl Transform for MyTransform {
    fn r#type(&self) -> TransformType {
        TransformType::MyTransform
    }

    fn transform(
        &self,
        _metadata: &TopicMetadata,
        mut message: DecodedMessage,
    ) -> Result<Option<DecodedMessage>, Error> {
        // Most transforms operate only on Payload::Json - non-JSON is a no-op.
        let Payload::Json(ref mut value) = message.payload else {
            return Ok(Some(message));
        };

        // Mutate `value` in place via simd_json::OwnedValue API.
        // Return Ok(None) to filter, Ok(Some(message)) to pass through.

        Ok(Some(message))
    }
}

2. Wire into transforms/mod.rs

Three edits, all required:

(a) Module declaration + re-export at the top:

mod my_transform;
pub use my_transform::{MyTransform, MyTransformConfig};

(b) Discriminant in TransformType:

pub enum TransformType {
    AddFields,
    // ...
    MyTransform,   // snake_case form in TOML: "my_transform"
}

(c) Branch in from_config():

TransformType::MyTransform => {
    // For new transforms, prefer Error::InvalidConfigValue with formatted context -
    // it preserves the underlying serde_json error. Existing code uses bare
    // Error::InvalidConfig for most variants (UnwrapEnvelope is the exception).
    let cfg: MyTransformConfig = serde_json::from_value(raw.clone())
        .map_err(|error| Error::InvalidConfigValue(format!("my_transform: {error}")))?;
    Ok(Arc::new(MyTransform::new(cfg)?))
}

from_config() returns Arc<dyn Transform> so the same instance is shared across the pipeline.

3. Document

Add a TOML usage example to sdk/README.md:

[transforms.my_transform]
enabled = true
key = "some_field"
option = true

4. Tests

Bottom of my_transform.rs:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn given_matching_field_should_transform() { ... }

    #[test]
    fn given_non_json_payload_should_passthrough() { ... }

    #[test]
    fn given_filter_condition_should_drop_message() { /* assert Ok(None) */ }

    #[test]
    fn given_invalid_config_should_error_in_new() {
        let cfg = MyTransformConfig { key: "".into(), option: None };
        assert!(matches!(MyTransform::new(cfg), Err(Error::InvalidConfigValue(_))));
    }

    fn topic_meta() -> TopicMetadata {
        TopicMetadata { stream: "s".into(), topic: "t".into() }
    }

    fn json_message(json: serde_json::Value) -> DecodedMessage { /* ... */ }
}

Rules

Operate on JSON only (mostly)

Most field-level transforms only make sense for Payload::Json. Convention (see add_fields.rs::AddFields::transform): silently pass through non-JSON:

let Payload::Json(ref mut value) = message.payload else {
    return Ok(Some(message));
};

Format-conversion transforms (proto_convert, avro_convert, flatbuffer_convert) are the exception - they explicitly convert between variants.

Mutate OwnedValue in place, don't clone-then-replace

simd_json::OwnedValue is mutable in place:

if let OwnedValue::Object(ref mut obj) = value {
    obj.insert(key.into(), new_value);
}

Do NOT clone the value, mutate the clone, then message.payload = Payload::Json(new). The whole point of &mut value is in-place mutation.

Filter semantics

Ok(None) drops the message silently - no error log, no metric. By design (filters succeed when they decide to drop). For visibility, log at debug! level.

Config validation

Validate in new() and return Err(Error::InvalidConfigValue(...)). Don't panic!. Don't validate on the hot path.

For transforms with regex or compiled patterns, compile once in new() and store the compiled form.

Stateless except for config

Transforms are constructed once at startup and Arc-cloned across tasks. Must be Send + Sync. No mutable runtime state - if you need accumulator-like behavior, that's not a Transform, that's a sink.

Compiled state goes in the struct, not behind a lock

Things derived from config (compiled regex, parsed paths, schema descriptors) should be computed in new() and stored as plain struct fields. The whole instance is shared via Arc<dyn Transform>, so per-field locking is unnecessary. See sdk/src/transforms/filter_fields.rs::FilterFields::new for the canonical pattern: regexes compile in new(), get stored as struct fields, are then read via &self.

transform() is sync

The trait method is sync (not async). Don't propose making it async - that would require pervasive change across the runtime. Schemas and other external resources should be loaded in new() (potentially via a fallible try_new), not lazily during transform().

Errors

ScenarioVariant
Config validation in new()Error::InvalidConfigValue("transform_name: ...")
Per-message validation (bad shape)Error::InvalidRecordValue("...")
Schema resolution failureError::InvalidTransformer
Format conversion failureError::InvalidPayloadType

Returning Err from transform() stops the message and is logged. For non-fatal field-level issues (field missing when transform expects it), prefer pass-through (Ok(Some(message))) unless config explicitly says to fail.

Common pitfalls

  1. Forgetting one of the three mod.rs edits - module declaration, TransformType variant, from_config() branch.
  2. #[serde(rename_all = "snake_case")] mismatch between enum variant name and TOML key.
  3. Cloning OwnedValue when in-place mutation would work.
  4. Validating config inside transform() instead of new().
  5. std::sync::Mutex for internal state on the hot path - precompute in new() and store as plain struct fields. Transforms run on every message. locks are wasted work.
  6. Returning Err for a filter - filters use Ok(None).

Before declaring done

cargo fmt --all
cargo sort --no-format --workspace
cargo clippy -p iggy_connector_sdk --all-targets --all-features -- -D warnings
cargo test -p iggy_connector_sdk --all-features

Verify by adding the transform to core/connectors/runtime/example_config/connectors/ and running the runtime - the transform should apply to each message.

If the transform changes payload shape sufficiently to break downstream sinks (e.g., renaming primary keys), call it out in the PR description.


Discussion / help: see AGENTS.md.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

コメント

ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。

まだコメントはありません。最初のコメントを書いてみませんか。

送信するとすぐに公開されます。メールアドレスや電話番号などの個人情報は書かないでください。URL は 2 つまでです。

0 / 1000 文字

このフォームは reCAPTCHA で保護されており、Google のプライバシーポリシーと利用規約が適用されます。

同じリポジトリのスキル

connector-review

無料概要あり

Apache Iggyのコネクター変更を複数の専門視点から厳しくレビューし、指摘ごとに独立した検証を行って、修正が必要な問題を整理します。

  • コネクターのPRをマージ前に確認したいとき
  • ブランチの変更を専門視点でレビューしたいとき
  • 指摘の妥当性を独立して検証したいとき
apache/iggy5,2232026年10月29日 更新

connector-runtime

無料概要あり

Apache Iggyとコネクタープラグインをつなぐランタイムの開発に使います。読み込みやデータ転送、状態保存、設定、HTTP API、メトリクスなどの変更箇所を把握できます。

  • プラグインの読み込みや管理を変更したいとき
  • ソース・シンクの転送処理を調べたいとき
  • 状態保存や設定取得を修正したいとき
apache/iggy5,2232026年10月29日 更新

connector-sdk

無料概要あり

Apache Iggy Connectors SDKのスキーマ、エンコーダー、デコーダー、エラー型やFFIなどを追加・変更するときに、関連箇所と互換性への影響を整理するスキルです。

  • SDKにスキーマ形式を追加したいとき
  • ストリーム変換器を実装したいとき
  • 公開APIやFFIの変更を検討するとき
apache/iggy5,2232026年10月29日 更新

connector-sink

無料概要あり

Apache Iggyのストリームからメッセージを受け取り、データベースやHTTPなど外部システムへ送るSinkプラグインの作成・変更を支援します。

  • DBへ書き込むSinkを作りたいとき
  • HTTP連携用のSinkを追加したいとき
  • リトライやバッチ処理を見直したいとき
apache/iggy5,2232026年10月29日 更新

connector-source

無料概要あり

データベース、API、キューなど外部システムをポーリングし、Apache Iggyのストリームへメッセージを送るSourceプラグインの作成・変更を支援します。

  • DBのデータを取り込むSourceを作りたいとき
  • APIの情報を定期取得したいとき
  • カーソルとACK・NACK処理を確認したいとき
apache/iggy5,2232026年10月29日 更新

connector-testing

無料概要あり

Apache Iggyのコネクター向けに、ユニットテストや実サービスを使う統合テストを設計・作成し、命名規則や状態復元などのテスト慣例に沿って確認します。

  • コネクターのユニットテストを追加したいとき
  • ソースの状態復元を検証したいとき
  • 実サービスを使う統合テストを書くとき
apache/iggy5,2232026年10月29日 更新

apache のスキルをすべて見る

このスキルの問題を報告する