1use axum::{
6 extract::{Path, Query, State},
7 http::StatusCode,
8 response::Json,
9};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::sync::Arc;
13
14use mockforge_core::consumer_contracts::{
15 Consumer, ConsumerBreakingChangeDetector, ConsumerIdentifier, ConsumerRegistry, ConsumerType,
16 ConsumerUsage, ConsumerViolation, UsageRecorder,
17};
18
19#[derive(Clone)]
21pub struct ConsumerContractsState {
22 pub registry: Arc<ConsumerRegistry>,
24 pub usage_recorder: Arc<UsageRecorder>,
26 pub detector: Arc<ConsumerBreakingChangeDetector>,
28}
29
30#[derive(Debug, Deserialize, Serialize)]
32pub struct RegisterConsumerRequest {
33 pub name: String,
35 pub consumer_type: String,
37 pub identifier: String,
39 pub workspace_id: Option<String>,
41 pub metadata: Option<HashMap<String, serde_json::Value>>,
43}
44
45#[derive(Debug, Serialize)]
47pub struct ConsumerResponse {
48 pub id: String,
50 pub name: String,
52 pub consumer_type: String,
54 pub identifier: String,
56 pub workspace_id: Option<String>,
58 pub created_at: i64,
60}
61
62#[derive(Debug, Deserialize)]
64pub struct ListConsumersRequest {
65 pub workspace_id: Option<String>,
67 pub consumer_type: Option<String>,
69 pub limit: Option<usize>,
71 pub offset: Option<usize>,
73}
74
75#[derive(Debug, Serialize)]
77pub struct ListConsumersResponse {
78 pub consumers: Vec<ConsumerResponse>,
80 pub total: usize,
82}
83
84#[derive(Debug, Serialize)]
86pub struct ConsumerUsageResponse {
87 pub consumer_id: String,
89 pub usage: Vec<ConsumerUsage>,
91}
92
93#[derive(Debug, Serialize)]
95pub struct ConsumerViolationsResponse {
96 pub consumer_id: String,
98 pub violations: Vec<ConsumerViolation>,
100}
101
102pub async fn register_consumer(
106 State(state): State<ConsumerContractsState>,
107 Json(request): Json<RegisterConsumerRequest>,
108) -> Result<Json<ConsumerResponse>, StatusCode> {
109 let consumer_type = match request.consumer_type.as_str() {
110 "workspace" => ConsumerType::Workspace,
111 "custom" => ConsumerType::Custom,
112 "api_key" => ConsumerType::ApiKey,
113 "auth_token" => ConsumerType::AuthToken,
114 _ => return Err(StatusCode::BAD_REQUEST),
115 };
116
117 let identifier = match consumer_type {
118 ConsumerType::Workspace => ConsumerIdentifier::workspace(request.identifier),
119 ConsumerType::Custom => ConsumerIdentifier::custom(request.identifier),
120 ConsumerType::ApiKey => ConsumerIdentifier::api_key(request.identifier),
121 ConsumerType::AuthToken => ConsumerIdentifier::auth_token(request.identifier),
122 };
123
124 let consumer = state
125 .registry
126 .get_or_create(identifier, request.name.clone(), request.workspace_id.clone())
127 .await;
128
129 Ok(Json(ConsumerResponse {
130 id: consumer.id,
131 name: consumer.name,
132 consumer_type: format!("{:?}", consumer.identifier.consumer_type),
133 identifier: consumer.identifier.value,
134 workspace_id: consumer.workspace_id,
135 created_at: consumer.created_at,
136 }))
137}
138
139pub async fn list_consumers(
143 State(state): State<ConsumerContractsState>,
144 Query(params): Query<HashMap<String, String>>,
145) -> Result<Json<ListConsumersResponse>, StatusCode> {
146 let mut consumers = state.registry.list_all().await;
147
148 if let Some(workspace_id) = params.get("workspace_id") {
150 consumers.retain(|c| c.workspace_id.as_ref().map(|w| w == workspace_id).unwrap_or(false));
151 }
152
153 if let Some(consumer_type_str) = params.get("consumer_type") {
154 let consumer_type = match consumer_type_str.as_str() {
155 "workspace" => ConsumerType::Workspace,
156 "custom" => ConsumerType::Custom,
157 "api_key" => ConsumerType::ApiKey,
158 "auth_token" => ConsumerType::AuthToken,
159 _ => return Err(StatusCode::BAD_REQUEST),
160 };
161 consumers.retain(|c| c.identifier.consumer_type == consumer_type);
162 }
163
164 let total = consumers.len();
165
166 let offset = params.get("offset").and_then(|s| s.parse().ok()).unwrap_or(0);
168 let limit = params.get("limit").and_then(|s| s.parse().ok()).unwrap_or(100);
169
170 consumers = consumers.into_iter().skip(offset).take(limit).collect();
171
172 let consumer_responses: Vec<ConsumerResponse> = consumers
173 .into_iter()
174 .map(|c| ConsumerResponse {
175 id: c.id,
176 name: c.name,
177 consumer_type: format!("{:?}", c.identifier.consumer_type),
178 identifier: c.identifier.value,
179 workspace_id: c.workspace_id,
180 created_at: c.created_at,
181 })
182 .collect();
183
184 Ok(Json(ListConsumersResponse {
185 consumers: consumer_responses,
186 total,
187 }))
188}
189
190pub async fn get_consumer(
194 State(state): State<ConsumerContractsState>,
195 Path(id): Path<String>,
196) -> Result<Json<ConsumerResponse>, StatusCode> {
197 let consumer = state.registry.get_by_id(&id).await.ok_or(StatusCode::NOT_FOUND)?;
198
199 Ok(Json(ConsumerResponse {
200 id: consumer.id,
201 name: consumer.name,
202 consumer_type: format!("{:?}", consumer.identifier.consumer_type),
203 identifier: consumer.identifier.value,
204 workspace_id: consumer.workspace_id,
205 created_at: consumer.created_at,
206 }))
207}
208
209pub async fn get_consumer_usage(
213 State(state): State<ConsumerContractsState>,
214 Path(id): Path<String>,
215) -> Result<Json<ConsumerUsageResponse>, StatusCode> {
216 state.registry.get_by_id(&id).await.ok_or(StatusCode::NOT_FOUND)?;
218
219 let usage = state.usage_recorder.get_usage(&id).await;
220
221 Ok(Json(ConsumerUsageResponse {
222 consumer_id: id,
223 usage,
224 }))
225}
226
227pub async fn get_consumer_violations(
231 State(_state): State<ConsumerContractsState>,
232 Path(_id): Path<String>,
233) -> Result<Json<ConsumerViolationsResponse>, StatusCode> {
234 Ok(Json(ConsumerViolationsResponse {
237 consumer_id: _id,
238 violations: vec![],
239 }))
240}
241
242pub fn consumer_contracts_router(state: ConsumerContractsState) -> axum::Router {
244 use axum::routing::{get, post};
245
246 axum::Router::new()
247 .route("/api/v1/consumers", post(register_consumer))
248 .route("/api/v1/consumers", get(list_consumers))
249 .route("/api/v1/consumers/{id}", get(get_consumer))
250 .route("/api/v1/consumers/{id}/usage", get(get_consumer_usage))
251 .route("/api/v1/consumers/{id}/violations", get(get_consumer_violations))
252 .with_state(state)
253}