Skip to content
This repository was archived by the owner on Dec 15, 2025. It is now read-only.

Commit fe1efb0

Browse files
committed
splitted api client and server + restructering plan + removed wrong tests
1 parent fa53489 commit fe1efb0

File tree

13 files changed

+1909
-536
lines changed

13 files changed

+1909
-536
lines changed

Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,6 @@ path = "src/lib.rs"
4141
name = "zinit"
4242
path = "src/main.rs"
4343
[[bin]]
44-
name = "testapp"
45-
path = "src/bin/testapp.rs"
46-
[[bin]]
4744
name = "zinit-http"
4845
path = "src/bin/zinit-http.rs"
4946

specs/api-restructuring-plan.md

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# API Restructuring Plan
2+
3+
## Current Structure Analysis
4+
5+
The current codebase has the following components:
6+
7+
1. **Zinit Process Manager**: The core process manager implemented in `src/zinit/` directory
8+
2. **API Implementation** (`src/app/api.rs`): Contains both server and client code
9+
- `Api` struct: Server implementation handling both JSON-RPC and legacy protocol over Unix socket
10+
- `Client` struct: Client implementation for communicating with the server
11+
3. **Zinit-client Library** (`zinit-client/src/lib.rs`): A separate client library providing a user-friendly interface
12+
4. **Zinit-HTTP Proxy** (`src/bin/zinit-http.rs`): HTTP proxy forwarding JSON-RPC requests to the Zinit Unix socket
13+
5. **CLI Interface** (`src/main.rs`): Command-line interface using the client to interact with Zinit
14+
15+
The main issue is that `src/app/api.rs` contains both server and client code, which should be separated for better maintainability and clearer architecture.
16+
17+
## Issues with Current Implementation
18+
19+
1. **Mixed Responsibilities**: The `api.rs` file handles both server and client functionality
20+
2. **Duplicate Client Implementations**: There are two client implementations - one in `api.rs` and another in `zinit-client/src/lib.rs`
21+
3. **Protocol Handling**: Both JSON-RPC and legacy protocol handling are mixed together
22+
23+
## Restructuring Plan
24+
25+
### 1. Create New File Structure
26+
27+
```
28+
src/
29+
├── app/
30+
│ ├── mod.rs
31+
│ ├── server.rs (new - extracted from api.rs)
32+
│ └── client.rs (new - extracted from api.rs)
33+
├── bin/
34+
│ └── zinit-http.rs (existing)
35+
└── ...
36+
37+
zinit-client/
38+
└── src/
39+
└── lib.rs (existing - will be updated to use client.rs)
40+
```
41+
42+
### 2. Split `api.rs` into Components
43+
44+
#### 2.1. `server.rs` - Server Component
45+
- Move the `Api` struct and its implementation
46+
- Keep JSON-RPC request handling
47+
- Keep legacy protocol handling
48+
- Keep Unix socket server implementation
49+
50+
#### 2.2. `client.rs` - Client Component
51+
- Move the `Client` struct and its implementation
52+
- Keep methods for interacting with the server
53+
- Support both JSON-RPC and legacy protocol
54+
55+
### 3. Update References and Dependencies
56+
57+
- Update `app/mod.rs` to expose both server and client modules
58+
- Update `zinit-client/src/lib.rs` to use the new client implementation
59+
- Update `src/bin/zinit-http.rs` to use the new client implementation
60+
61+
### 4. Ensure Protocol Support
62+
63+
- Maintain support for the legacy protocol over Unix socket
64+
- Maintain support for JSON-RPC over Unix socket
65+
- Ensure the HTTP proxy correctly forwards JSON-RPC requests
66+
67+
## Detailed Implementation Plan
68+
69+
### Step 1: Create `server.rs`
70+
71+
Extract the server-related code from `api.rs`:
72+
- JSON-RPC structures (request, response, error)
73+
- Error codes
74+
- `Api` struct and its implementation
75+
- Server-side protocol handling
76+
77+
### Step 2: Create `client.rs`
78+
79+
Extract the client-related code from `api.rs`:
80+
- `Client` struct and its implementation
81+
- Client-side protocol handling
82+
- Methods for interacting with the server
83+
84+
### Step 3: Update `mod.rs`
85+
86+
Update `app/mod.rs` to expose both modules:
87+
```rust
88+
pub mod server;
89+
pub mod client;
90+
```
91+
92+
### Step 4: Update References
93+
94+
- Update all references to `api::Api` to `server::Api`
95+
- Update all references to `api::Client` to `client::Client`
96+
97+
### Step 5: Update `zinit-client/src/lib.rs`
98+
99+
- Refactor to use the new client implementation
100+
- Ensure it supports both Unix socket and HTTP transport
101+
102+
### Step 6: Update `zinit-http.rs`
103+
104+
- Update to use the new client implementation
105+
- Ensure it correctly forwards JSON-RPC requests
106+
107+
## Architecture Diagram
108+
109+
```mermaid
110+
graph TD
111+
A[Zinit Process Manager] --> B[Server Component]
112+
B --> C[Unix Socket]
113+
C --> D[Client Component]
114+
C --> E[Zinit-HTTP Proxy]
115+
E --> F[HTTP Endpoint]
116+
F --> G[Zinit-Client Library]
117+
D --> H[CLI]
118+
119+
subgraph "src/app/"
120+
B[Server Component]
121+
D[Client Component]
122+
end
123+
124+
subgraph "src/bin/"
125+
E[Zinit-HTTP Proxy]
126+
end
127+
128+
subgraph "zinit-client/"
129+
G[Zinit-Client Library]
130+
end
131+
132+
subgraph "src/"
133+
H[CLI]
134+
end
135+
```
136+
137+
## Benefits of This Restructuring
138+
139+
1. **Clear Separation of Concerns**: Server and client code are separated
140+
2. **Improved Maintainability**: Each component has a single responsibility
141+
3. **Better Code Organization**: Related functionality is grouped together
142+
4. **Reduced Duplication**: Client code is centralized
143+
5. **Clearer Architecture**: The relationship between components is more explicit
144+
145+
## Implementation Details
146+
147+
### server.rs
148+
149+
The `server.rs` file will contain:
150+
151+
1. JSON-RPC structures:
152+
- `JsonRpcRequest`
153+
- `JsonRpcResponse`
154+
- `JsonRpcError`
155+
- `JsonRpcBatchRequest`
156+
157+
2. Error codes:
158+
- Standard JSON-RPC error codes
159+
- Custom error codes for Zinit
160+
161+
3. Legacy protocol structures:
162+
- `ZinitResponse`
163+
- `ZinitState`
164+
- `Status`
165+
166+
4. The `Api` struct and its implementation:
167+
- Constructor
168+
- Unix socket server
169+
- JSON-RPC request handling
170+
- Legacy protocol handling
171+
- Service management methods
172+
173+
### client.rs
174+
175+
The `client.rs` file will contain:
176+
177+
1. The `Client` struct and its implementation:
178+
- Constructor
179+
- Connection handling
180+
- JSON-RPC request handling
181+
- Legacy protocol handling
182+
- Service management methods
183+
184+
### mod.rs
185+
186+
The `mod.rs` file will be updated to:
187+
188+
```rust
189+
pub mod server;
190+
pub mod client;
191+
```
192+
193+
And all functions that currently use `api::Api` or `api::Client` will be updated to use `server::Api` or `client::Client`.
194+
195+
### zinit-client/src/lib.rs
196+
197+
The `zinit-client/src/lib.rs` file will be updated to:
198+
199+
1. Import the new client implementation
200+
2. Ensure it supports both Unix socket and HTTP transport
201+
3. Maintain the same public API
202+
203+
### zinit-http.rs
204+
205+
The `zinit-http.rs` file will be updated to:
206+
207+
1. Import the new client implementation
208+
2. Ensure it correctly forwards JSON-RPC requests
209+
210+
## Next Steps
211+
212+
1. Create the new files
213+
2. Split the code
214+
3. Update references
215+
4. Test the implementation

src/app/api.rs

Lines changed: 148 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,23 @@ impl Client {
864864

865865
// Send a JSON-RPC request and return the result
866866
async fn jsonrpc_request(&self, method: &str, params: Option<Value>) -> Result<Value> {
867+
// First try JSON-RPC
868+
let result = self.try_jsonrpc(method, params.clone()).await;
869+
870+
// If JSON-RPC fails, try legacy protocol
871+
if let Err(e) = &result {
872+
if e.to_string().contains("Invalid JSON-RPC response") ||
873+
e.to_string().contains("Failed to parse") {
874+
debug!("JSON-RPC failed, trying legacy protocol: {}", e);
875+
return self.try_legacy_protocol(method, params).await;
876+
}
877+
}
878+
879+
result
880+
}
881+
882+
// Try using JSON-RPC protocol
883+
async fn try_jsonrpc(&self, method: &str, params: Option<Value>) -> Result<Value> {
867884
// Get a unique ID for this request
868885
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
869886

@@ -882,8 +899,6 @@ impl Client {
882899
con.flush().await?;
883900

884901
// Read and parse the response
885-
// The server sends a JSON response followed by a newline character
886-
// We need to read the entire response until we find the terminating newline
887902
let mut buffer = Vec::new();
888903
let mut temp_buf = [0u8; 1024];
889904

@@ -918,8 +933,137 @@ impl Client {
918933
}
919934
}
920935

921-
// Keep the original command method for backward compatibility if needed
922-
#[allow(dead_code)]
936+
// Try to use the legacy protocol as a fallback
937+
async fn try_legacy_protocol(&self, method: &str, params: Option<Value>) -> Result<Value> {
938+
// Convert JSON-RPC method and params to legacy command
939+
let cmd = match method {
940+
"service.list" => "list".to_string(),
941+
"system.shutdown" => "shutdown".to_string(),
942+
"system.reboot" => "reboot".to_string(),
943+
"service.status" => {
944+
if let Some(params) = params {
945+
if let Some(name) = params.get("name").and_then(|v| v.as_str()) {
946+
format!("status {}", name)
947+
} else {
948+
bail!("Missing or invalid 'name' parameter");
949+
}
950+
} else {
951+
bail!("Missing parameters");
952+
}
953+
},
954+
"service.start" => {
955+
if let Some(params) = params {
956+
if let Some(name) = params.get("name").and_then(|v| v.as_str()) {
957+
format!("start {}", name)
958+
} else {
959+
bail!("Missing or invalid 'name' parameter");
960+
}
961+
} else {
962+
bail!("Missing parameters");
963+
}
964+
},
965+
"service.stop" => {
966+
if let Some(params) = params {
967+
if let Some(name) = params.get("name").and_then(|v| v.as_str()) {
968+
format!("stop {}", name)
969+
} else {
970+
bail!("Missing or invalid 'name' parameter");
971+
}
972+
} else {
973+
bail!("Missing parameters");
974+
}
975+
},
976+
"service.forget" => {
977+
if let Some(params) = params {
978+
if let Some(name) = params.get("name").and_then(|v| v.as_str()) {
979+
format!("forget {}", name)
980+
} else {
981+
bail!("Missing or invalid 'name' parameter");
982+
}
983+
} else {
984+
bail!("Missing parameters");
985+
}
986+
},
987+
"service.monitor" => {
988+
if let Some(params) = params {
989+
if let Some(name) = params.get("name").and_then(|v| v.as_str()) {
990+
format!("monitor {}", name)
991+
} else {
992+
bail!("Missing or invalid 'name' parameter");
993+
}
994+
} else {
995+
bail!("Missing parameters");
996+
}
997+
},
998+
"service.kill" => {
999+
if let Some(params) = params {
1000+
if let Some(name) = params.get("name").and_then(|v| v.as_str()) {
1001+
if let Some(signal) = params.get("signal").and_then(|v| v.as_str()) {
1002+
format!("kill {} {}", name, signal)
1003+
} else {
1004+
bail!("Missing or invalid 'signal' parameter");
1005+
}
1006+
} else {
1007+
bail!("Missing or invalid 'name' parameter");
1008+
}
1009+
} else {
1010+
bail!("Missing parameters");
1011+
}
1012+
},
1013+
"service.create" => {
1014+
// The legacy protocol doesn't directly support service creation
1015+
bail!("Service creation not supported in legacy protocol");
1016+
},
1017+
"service.delete" => {
1018+
// The legacy protocol doesn't directly support service deletion
1019+
bail!("Service deletion not supported in legacy protocol");
1020+
},
1021+
"service.get" => {
1022+
// The legacy protocol doesn't directly support getting service config
1023+
bail!("Getting service configuration not supported in legacy protocol");
1024+
},
1025+
"rpc.discover" => {
1026+
// This is a JSON-RPC specific method with no legacy equivalent
1027+
bail!("RPC discovery not supported in legacy protocol");
1028+
},
1029+
"service.restart" => {
1030+
if let Some(params) = params {
1031+
if let Some(name) = params.get("name").and_then(|v| v.as_str()) {
1032+
format!("restart {}", name)
1033+
} else {
1034+
bail!("Missing or invalid 'name' parameter");
1035+
}
1036+
} else {
1037+
bail!("Missing parameters");
1038+
}
1039+
},
1040+
"log" => {
1041+
if let Some(params) = params {
1042+
if let Some(filter) = params.get("filter").and_then(|v| v.as_str()) {
1043+
if let Some(snapshot) = params.get("snapshot").and_then(|v| v.as_bool()) {
1044+
if snapshot {
1045+
format!("log snapshot {}", filter)
1046+
} else {
1047+
format!("log {}", filter)
1048+
}
1049+
} else {
1050+
format!("log {}", filter)
1051+
}
1052+
} else {
1053+
"log".to_string()
1054+
}
1055+
} else {
1056+
"log".to_string()
1057+
}
1058+
},
1059+
_ => bail!("Unsupported method for legacy protocol: {}", method),
1060+
};
1061+
1062+
// Use the command method to send the legacy command
1063+
self.command(&cmd).await
1064+
}
1065+
1066+
// Command method for the legacy protocol
9231067
async fn command(&self, c: &str) -> Result<Value> {
9241068
let mut con = BufStream::new(self.connect().await?);
9251069

0 commit comments

Comments
 (0)