Guia — IStep

Um passo novo de pipeline, ativado por uma chave em type: no YAML. É o ponto de extensão mais usado.

Exemplo mínimo

using System.Text.Json.Nodes;
using Pipevine.Abstractions;

// A chave usada no YAML: `type: acme.echo`
[Step("acme.echo")]
public sealed class EchoStepFactory : IStepFactory
{
    // Roda UMA vez, no startup. Valida a config, resolve o que precisa ser resolvido uma
    // vez só. Lançar aqui derruba a aplicação — não a primeira requisição.
    public IStep Build(StepBuildContext context)
    {
        var message = context.Config["message"]?.GetValue<string>()
            ?? throw new InvalidOperationException("'acme.echo' requer 'message'.");

        return new EchoStep(message);
    }
}

// Imutável, thread-safe, reusado por toda requisição — nenhum campo mutável (PV4002 pega isso).
internal sealed class EchoStep(string message) : IStep
{
    public ValueTask<StepOutcome> ExecuteAsync(PipelineContext ctx, CancellationToken ct)
    {
        ctx.Vars()["echo"] = JsonValue.Create(message);
        return ValueTask.FromResult(StepOutcome.Continue);
    }
}
pipeline:
  - type: acme.echo
    message: "hello"

Ativação

  1. Referenciar o pacote (<PackageReference Include="Acme.Pipevine.Extension" />).

  2. Nenhum passo 2 para o caminho compilado — [Step("acme.echo")] é descoberto automaticamente pelo gerador de código a partir das referências de compilação, então o step já é validado em tempo de build (um type: desconhecido vira o diagnóstico PV3001, apontando para o YAML) sem nenhum registro manual.

  3. Mas para o modo interpretado em runtime, o autor da aplicação ainda precisa registrar a factory no DI — por convenção, num método AddXxx() que o próprio pacote expõe:

    services.AddKeyedSingleton<IStepFactory, EchoStepFactory>("acme.echo");
    

    A razão para esse passo extra: [Step] alimenta só o catálogo de compile-time do gerador de código; o registro que resolve steps em runtime para o caminho interpretado é um mecanismo de DI separado, resolvido por chave. As duas coisas coexistem porque servem dois caminhos de execução diferentes — compilado e interpretado — e cada um precisa do seu próprio registro.

Validação de config com schema (opcional)

Implemente IStepConfigSchema ao lado de IStepFactory para declarar um JSON Schema do bloco config: — sem isso o step funciona normalmente, só perde a validação de YAML no formato de schema (PV4010 avisa, não bloqueia):

public sealed class EchoStepFactory : IStepFactory, IStepConfigSchema
{
    public string ConfigJsonSchema =>
        """{ "type": "object", "required": ["message"], "properties": { "message": { "type": "string" } } }""";

    public IStep Build(StepBuildContext context) { /* ... */ }
}

Participando da geração de código (opcional)

Um step pode implementar IStepCodeEmitter para emitir C# direto em vez do caminho interpretado genérico dentro de um pipeline gerado — ver IStepCodeEmitter. Nenhum step é obrigado a isso: extensibilidade nunca é bloqueada por performance.

Diagnósticos para quem escreve o step

Referencie o analisador Roslyn do Pipevine (PrivateAssets="all") no .csproj do seu pacote de extensão:

  • PV4001IStepFactory sem [Step].
  • PV4002 — campo ou propriedade mutável numa implementação de IStep.
  • PV4010IStepFactory sem IStepConfigSchema (aviso).
  • PV4020 — chave de step colidindo com uma já registrada, sua ou de um pacote referenciado.

Para onde ir daqui