Install
$ agentstack add skill-snk-devcenter-addon-studio-retrofit ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Integração HTTP com Retrofit — Addon Studio 2.0
Stack oficial para integração HTTP externa: Retrofit + Moshi + OkHttp. Substitui HttpClient nativo, URLConnection, Apache HTTP Client.
Esta skill cobre: declaração de deps no Gradle, interface client, wiring Guice, interceptors. Decisões arquiteturais (factory wrapper vs builder direto, retry, URL dinâmica) são do projeto — a skill mostra patterns válidos sem opinar.
1. Dependências (build.gradle do módulo)
> NÃO declarar no build.gradle raiz. Vai no build.gradle do módulo (model/build.gradle, etc.).
Use somente moduleLib — a configuração custom do plugin br.com.sankhya.addonstudio já adiciona o JAR ao classpath de compilação e empacota no EJB final. Não duplicar com implementation.
dependencies {
// Retrofit + Moshi (JSON) — padrão para APIs REST.
moduleLib 'com.squareup.retrofit2:retrofit:2.12.0'
moduleLib 'com.squareup.retrofit2:converter-moshi:2.12.0'
moduleLib 'com.squareup.moshi:moshi:1.15.2'
// OkHttp — cliente HTTP subjacente (obrigatório).
moduleLib 'com.squareup.okhttp3:okhttp:3.14.9'
}
Conversores opcionais
| Conversor | Quando usar | Dependência | |:----------|:------------|:------------| | converter-moshi | JSON (padrão) | com.squareup.retrofit2:converter-moshi:2.12.0 | | converter-scalars | Body String cru (SOAP/XML/texto) | com.squareup.retrofit2:converter-scalars:2.12.0 | | converter-jaxb | XML via JAXB | usar fábrica externa (JaxbConverterFactory de pacote terceiro) |
> Para testes com MockWebServer, adicionar em testImplementation: > ``groovy > testImplementation 'com.squareup.okhttp3:mockwebserver:3.14.9' > ``
2. Interface do client
Declare a API como interface anotada com Retrofit. Sempre retorno Call (síncrono — executor trata .execute()).
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.Query;
public interface ParceiroApi {
@GET("v1/produtos/{id}")
Call buscarProduto(@Path("id") Long id);
@GET("v1/produtos")
Call listarProdutos(@Query("limit") Integer limit,
@Query("offset") Integer offset);
@POST("v1/pedidos")
@Headers("Content-Type: application/json")
Call criarPedido(@Body NovoPedidoDto pedido);
}
Anotações principais
| Anotação | Uso | |:---------|:----| | @GET/@POST/@PUT/@DELETE/@PATCH | Verbo HTTP + path relativo (v1/recurso/{id}) | | @Path("id") | Substitui {id} no path | | @Query("k") | Adiciona ?k=v na query string | | @QueryMap | Map dinâmico de query params | | @Body | Corpo JSON serializado pelo conversor (Moshi) | | @Headers("K: V") | Header estático no método | | @Header("K") | Header dinâmico (parâmetro) | | @FormUrlEncoded + @Field | application/x-www-form-urlencoded | | @Multipart + @Part | Upload multipart/form-data |
DTOs (Moshi)
DTOs são POJOs Java 8 com Lombok. Moshi serializa via getters/setters — nomes de campo precisam bater com JSON (use @Json(name = "...") se diferente).
import com.squareup.moshi.Json;
import lombok.Data;
@Data
public class ProdutoDto {
private Long id;
private String nome;
@Json(name = "preco_unitario")
private Double precoUnitario;
}
3. Wiring Guice — duas opções
Escolha conforme o projeto. Skill não opina — ambas são válidas.
Opção A — Retrofit.Builder direto no @Provides
Indicado quando addon tem uma única integração HTTP ou cada cliente tem config muito diferente. Sem boilerplate adicional.
import br.com.sankhya.studio.stereotypes.CustomModule;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.moshi.MoshiConverterFactory;
import java.util.concurrent.TimeUnit;
@CustomModule
public class ParceiroIntegrationModule extends AbstractModule {
private static final String BASE_URL = "https://api.parceiro.com.br/";
@Override
protected void configure() { }
@Provides
@Singleton
public ParceiroApi provideParceiroApi(ParceiroAuthInterceptor authInterceptor) {
OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(authInterceptor)
.build();
return new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(httpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
.create(ParceiroApi.class);
}
}
Opção B — Factory wrapper reutilizável
Indicado quando addon tem múltiplas integrações HTTP — encapsula timeouts/converter padrão em um lugar só, deixa @Provides enxuto. Trocar a lib base no futuro vira mudança em arquivo único.
Boilerplate:
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.converter.moshi.MoshiConverterFactory;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Factory centralizada de clientes Retrofit. Injetável via Guice
* (classe concreta sem deps — auto-resolvida).
*/
public class RetrofitClientFactory {
/** JSON (Moshi) — padrão REST. */
public T create(Class service, List interceptors, String baseUrl) {
return create(service, interceptors, baseUrl, MoshiConverterFactory.create());
}
/** Conversor customizado (XML, scalars, etc.). */
public T create(Class service, List interceptors, String baseUrl,
Converter.Factory converterFactory) {
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS);
for (Interceptor interceptor : interceptors) {
clientBuilder.addInterceptor(interceptor);
}
return new Retrofit.Builder()
.baseUrl(baseUrl)
.client(clientBuilder.build())
.addConverterFactory(converterFactory)
.build()
.create(service);
}
}
Uso no módulo:
@CustomModule
public class ParceiroIntegrationModule extends AbstractModule {
private static final String BASE_URL = "https://api.parceiro.com.br/";
@Override
protected void configure() { }
@Provides
@Singleton
public ParceiroApi provideParceiroApi(RetrofitClientFactory factory,
ParceiroAuthInterceptor authInterceptor) {
return factory.create(
ParceiroApi.class,
Collections.singletonList(authInterceptor),
BASE_URL
);
}
}
Regras universais (qualquer opção)
@CustomModule+extends AbstractModule— sem essa dupla, Guice ignora o módulo.@Provides @Singleton— cliente Retrofit é stateless, reutilizar instância evita reconstruir OkHttp pool a cada chamada.- Interceptors recebidos por parâmetro do método
@Provides— Guice resolve automático. - Ver skill
dependency-injectionpara detalhes de@CustomModule,@Provides,Multibinder.
4. Interceptors OkHttp
Interceptors são pipeline que processa cada request/response. Use para autenticação, logging, headers globais, retry, métricas.
Estrutura básica
import br.com.sankhya.studio.stereotypes.Component;
import com.google.inject.Singleton;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
@Component
@Singleton
public class ParceiroAuthInterceptor implements Interceptor {
private final TokenProvider tokenProvider;
@com.google.inject.Inject
public ParceiroAuthInterceptor(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request authenticated = original.newBuilder()
.header("Authorization", "Bearer " + tokenProvider.getToken())
.build();
return chain.proceed(authenticated);
}
}
Regras:
@Component @Singleton— uma instância para todas as chamadas.- Implementa
okhttp3.Interceptor. chain.proceed(request)é obrigatório — sem ele a request não vai pra rede.@Injectvia construtor para resolver dependências (token provider, config, etc.).
Dependência circular (interceptor → client → interceptor)
Comum quando o interceptor precisa de um client que precisa do próprio interceptor (refresh token, por exemplo). Use Provider:
@Singleton
public class ParceiroAuthInterceptor implements Interceptor {
private final Provider authClientProvider;
@Inject
public ParceiroAuthInterceptor(Provider authClientProvider) {
this.authClientProvider = authClientProvider;
}
@Override
public Response intercept(Chain chain) throws IOException {
ParceiroAuthClient client = authClientProvider.get(); // resolve lazy
// ...
}
}
Ver skill dependency-injection (seção Provider).
Múltiplos interceptors
Ordem importa. Passe lista na ordem desejada:
factory.create(
ParceiroApi.class,
Arrays.asList(loggingInterceptor, authInterceptor, headerInterceptor),
BASE_URL
);
Pipeline OkHttp: cada interceptor envolve o próximo (request desce, response sobe).
> Atenção — segundo interceptor @Component quebra o deploy: cada @Component é bindado automaticamente às interfaces que implementa; dois @Component implements Interceptor no mesmo addon geram [Guice/BindingAlreadySet]: okhttp3.Interceptor was bound multiple times na subida do Wildfly (build e testes passam). A partir do segundo interceptor, remova o stereotype e proveja via @Provides @Singleton do tipo concreto — ver skill dependency-injection (seção 9).
5. Executando chamadas
O retorno Call é preguiçoso — só executa quando você chama .execute() (sync) ou .enqueue() (async).
Execução simples (sync)
import retrofit2.Call;
import retrofit2.Response;
@Component
public class ParceiroGateway {
private final ParceiroApi api;
@Inject
public ParceiroGateway(ParceiroApi api) {
this.api = api;
}
public ProdutoDto buscar(Long id) {
try {
Response response = api.buscarProduto(id).execute();
if (!response.isSuccessful()) {
throw new IntegrationApiException(
"Erro " + response.code() + ": " + (response.errorBody() != null ? response.errorBody().string() : "")
);
}
ProdutoDto body = response.body();
if (body == null) {
throw new IntegrationApiException("Resposta com corpo nulo");
}
return body;
} catch (IOException e) {
throw new IntegrationNetworkException("Falha de rede", e);
}
}
}
> Boilerplate de tratamento (status, body nulo, IOException) repete em toda chamada. Projetos costumam extrair um RetrofitCallExecutor — ver bloco "Patterns avançados" abaixo.
6. Configuração de base URL
Três abordagens válidas. Skill não opina.
| Abordagem | Quando usar | |:----------|:------------| | Constante static final no módulo | URL fixa, mudou só com release | | Campo @Value(value = "INTEGRATION_PARCEIRO_URL", type = ValueType.ENV_VAR, defaultValue = "...") num @Component de config, injetado como parâmetro do @Provides | URL varia por ambiente (dev/homol/prod) — sintaxe completa na skill value | | Tabela de configuração no banco | URL gerenciada pelo cliente final em runtime — usar factory pattern (avançado) |
7. Patterns avançados — menção curta
Estes patterns são comuns mas opcionais. Skill não detalha — implemente conforme a necessidade do projeto.
- URL dinâmica (config no banco): crie interface funcional
ParceiroApiFactory { ParceiroApi create(String urlBase); }e binde via@Providesretornando lambda. Útil quando gateway lê URL deConfiguracaoPlataforma. - Retry com backoff: envolva
call.execute()em umRetryExecutorpróprio — distinga exceções de rede (retry) das de API (não retry). Considereokhttp3.ConnectionPoole timeouts antes de retry. - Logging:
okhttp3.logging.HttpLoggingInterceptor(dep adicionalmoduleLib 'com.squareup.okhttp3:logging-interceptor:3.14.9'). Configurar nívelBODYsó em dev. - SOAP/XML: use
converter-scalars+ interceptor que envelopa request em `e extraida response. DTOs ficam só com conteúdo funcional. Anote método Retrofit com@Headers("SOAPAction: ...")`. - Executor compartilhado:
RetrofitCallExecutor(@Component @Singleton) que recebeCalle devolveT, centralizando tratamento de status/body/IOException. Reduz boilerplate em gateways.
8. Erros Comuns
| Erro | Causa | Correção | |:-----|:------|:---------| | NoClassDefFoundError: retrofit2/Retrofit em runtime | Declarado só como implementation, sem moduleLib | Trocar para moduleLib (ou ambos em versões antigas do plugin Gradle) | | IllegalArgumentException: Illegal URL no Retrofit.Builder | Base URL sem barra final | Adicionar / no fim: "https://api.x.com/" | | EOFException em response com 204 No Content | Tentou desserializar corpo vazio | Use Call ou cheque response.body() == null | | MalformedJsonException ao deserializar | DTO não bate com JSON | Adicionar @Json(name = "...") em campos com nome diferente | | @Inject de javax.inject no interceptor | Pacote errado | com.google.inject.Inject (ver skill addon-studio) | | Token expirou mid-request | Auth interceptor sem refresh | Implementar okhttp3.Authenticator separado para 401, ou refresh proativo | | Timeouts disparam sob carga | Defaults OkHttp são baixos | Configurar connectTimeout/readTimeout/writeTimeout no OkHttpClient.Builder | | Method not annotated with HTTP method type | Falta @GET/@POST/etc. | Adicionar verbo HTTP no método da interface |
9. Checklist
Nova integração HTTP
- [ ] Adicionar deps
moduleLib(retrofit + converter-moshi + moshi + okhttp) nobuild.gradledo módulo. - [ ] Criar interface
XxxApicom anotações Retrofit. RetornoCall. - [ ] Criar DTOs Java 8 com Lombok (
@Data) — usar@Json(name=...)quando JSON não casar. - [ ] Criar
@CustomModulecom@Provides @Singletonretornando o cliente. - [ ] Se múltiplas APIs no addon, considerar
RetrofitClientFactory(opção B). - [ ] Criar interceptors necessários (auth, logging) como
@Component @Singleton. - [ ] Gateway/adapter (
@Component) injeta o client e expõe métodos de domínio. - [ ] Tratar response status, body nulo,
IOException— extrair executor compartilhado se boilerplate repetir.
Skills relacionadas
dependency-injection—@CustomModule,@Provides,@Singleton,Provider,Multibindervalue— injetar base URL e timeouts via@Valueaddon-studio— regras universais (Java 8,@Injectdecom.google.inject)test— testar gateway comMockWebServer(OkHttp)controller-advice— mapearIntegrationApiException/IntegrationNetworkExceptionpara HTTP errors do addonbuild— comandos Gradle (gradle deployAddon)encoding— garantir ISO-8859-1 após criar arquivos.java
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: snk-devcenter
- Source: snk-devcenter/addon-studio
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.