migrate to gitea

This commit is contained in:
jonasgaudian
2026-02-13 00:15:36 +01:00
commit 269cc9e417
407 changed files with 66841 additions and 0 deletions

View File

@@ -0,0 +1,368 @@
# API Request Architecture Documentation
## Overview
This document explains how to create and handle JSON-based API requests in the Translator application. The architecture has been refactored to use a unified, type-safe template system that ensures consistency and maintainability.
## Architecture Components
### 1. JsonHelper
**Location**: `app/src/main/java/eu/gaudian/translator/utils/JsonHelper.kt`
The JsonHelper provides unified JSON parsing, validation, and error handling for all API responses.
**Key Methods**:
```kotlin
// Parse JSON with comprehensive error handling
fun <T> parseJson(json: String, serializer: KSerializer<T>, serviceName: String): Result<T>
// Validate required fields exist in JSON
fun validateRequiredFields(json: String, requiredFields: List<String>): Boolean
// Extract specific field value
fun extractField(json: String, fieldName: String): String?
// Clean and validate JSON string
fun cleanAndValidateJson(json: String): String
```
### 2. ApiRequestHandler
**Location**: `app/src/main/java/eu/gaudian/translator/utils/ApiRequestHandler.kt`
The single entry point for all API calls. This is the ONLY component that should call `ApiManager.getCompletion()`.
**Key Methods**:
```kotlin
// Preferred method - use templates
suspend fun <T> executeRequest(template: ApiRequestTemplate<T>): Result<T>
// Legacy method - deprecated
suspend fun <T> executeRequest(prompt: String, serializer: KSerializer<T>, modelType: ModelType): Result<T>
// Text-only requests
suspend fun executeTextRequest(prompt: String, modelType: ModelType): Result<String>
```
### 3. ApiRequestTemplates
**Location**: `app/src/main/java/eu/gaudian/translator/utils/ApiRequestTemplates.kt`
Type-safe request templates that define the structure and validation for different API operations.
## Creating New JSON API Requests
### Step 1: Define Your Response Data Class
Create a serializable data class for your API response:
```kotlin
@kotlinx.serialization.Serializable
data class MyApiResponse(
val requiredField: String,
val optionalField: Int? = null,
val items: List<MyItem> = emptyList()
)
@kotlinx.serialization.Serializable
data class MyItem(
val id: String,
val name: String,
val value: Double
)
```
### Step 2: Create a Request Template
Extend `BaseApiRequestTemplate<T>` for type-safe requests:
```kotlin
class MyApiRequest(
private val parameter1: String,
private val parameter2: Int,
private val language: String
) : BaseApiRequestTemplate<MyApiResponse>() {
override val responseSerializer = MyApiResponse.serializer()
override val modelType = ModelType.TRANSLATION // Choose appropriate type
override val serviceName = "MyService"
override val requiredFields = listOf("requiredField") // Fields that must exist
init {
promptBuilder.basePrompt = "Perform my API operation with $parameter1 and $parameter2"
addDetail("Language: $language")
addDetail("Parameter 2 value: $parameter2")
withJsonResponse("a JSON object with 'requiredField' (string), 'optionalField' (integer), and 'items' array")
}
}
```
### Step 3: Use the Template in Your Service
```kotlin
class MyService(context: Context) {
private val apiRequestHandler = ApiRequestHandler(ApiManager(context), context)
suspend fun performMyOperation(param1: String, param2: Int, language: String): Result<MyApiResponse> {
return try {
Log.i("MyService", "Performing operation with $param1, $param2 in $language")
val template = MyApiRequest(
parameter1 = param1,
parameter2 = param2,
language = language
)
val result = apiRequestHandler.executeRequest(template)
result.map { response ->
Log.i("MyService", "Successfully completed operation")
response
}.onFailure { exception ->
Log.e("MyService", "Failed to perform operation", exception)
}
} catch (e: Exception) {
Log.e("MyService", "Unexpected error in performMyOperation", e)
Result.failure(e)
}
}
}
```
## Available Model Types
Choose the appropriate `ModelType` for your request:
- `ModelType.TRANSLATION` - For translation-related requests
- `ModelType.DICTIONARY` - For dictionary and vocabulary requests
- `ModelType.VOCABULARY` - For vocabulary generation and analysis
- `ModelType.EXERCISE` - For exercise generation
- `ModelType.CORRECTION` - For text correction
## JSON Response Structure Guidelines
### 1. Always Define Required Fields
```kotlin
override val requiredFields = listOf("id", "name", "data")
```
### 2. Use Clear Field Names
```kotlin
// Good
data class Response(val userId: String, val userName: String, val isActive: Boolean)
// Avoid
data class Response(val uid: String, val nm: String, val active: Boolean)
```
### 3. Include Type Information in Prompts
```kotlin
withJsonResponse("a JSON object with 'userId' (string), 'userName' (string), 'isActive' (boolean), and 'createdAt' (timestamp)")
```
## Error Handling Best Practices
### 1. Always Wrap in Try-Catch
```kotlin
suspend fun myMethod(): Result<MyType> = withContext(Dispatchers.IO) {
try {
// Your logic here
result.map { response ->
// Success handling
response
}.onFailure { exception ->
// Failure logging
Log.e("MyService", "Operation failed", exception)
}
} catch (e: Exception) {
Log.e("MyService", "Unexpected error", e)
Result.failure(e)
}
}
```
### 2. Log at Appropriate Levels
```kotlin
Log.i("ServiceName", "Starting operation with parameters: $param1, $param2") // Info level
Log.d("ServiceName", "Generated prompt: $prompt") // Debug level
Log.e("ServiceName", "Operation failed", exception) // Error level
```
### 3. Validate Responses
```kotlin
// The template automatically validates required fields
// But you can add custom validation if needed
result.map { response ->
if (response.items.isEmpty()) {
Log.w("MyService", "API returned empty items array")
}
response
}
```
## Testing Your API Requests
### 1. Unit Test Your Template
```kotlin
@Test
fun `MyApiRequest should build correct prompt`() {
val template = MyApiRequest("test", 42, "English")
val prompt = template.buildPrompt()
assertTrue(prompt.contains("test"))
assertTrue(prompt.contains("42"))
assertTrue(prompt.contains("English"))
assertTrue(prompt.contains("JSON object"))
}
```
### 2. Test JSON Parsing
```kotlin
@Test
fun `JsonHelper should parse valid response`() {
val json = """{"requiredField": "value", "optionalField": 123}"""
val result = jsonHelper.validateRequiredFields(json, listOf("requiredField"))
assertTrue(result)
}
```
### 3. Integration Test
```kotlin
@Test
fun `end to end API request should work`() = runTest {
// Mock the API manager
every {
apiManager.getCompletion(
prompt = any(),
callback = any(),
modelType = ModelType.TRANSLATION
)
} answers {
val callback = thirdArg<(String?) -> Unit>()
callback("""{"requiredField": "test", "optionalField": 123}""")
}
val template = MyApiRequest("test", 42, "English")
val result = apiRequestHandler.executeRequest(template)
assertTrue(result.isSuccess)
assertEquals("test", result.getOrNull()?.requiredField)
}
```
## Migration from Legacy Code
### Before (Legacy):
```kotlin
val prompt = PromptBuilder("Do something")
.addDetail("Parameter: $param")
.withJsonResponse("JSON format")
.build()
apiRequestHandler.executeRequest(prompt, MyResponse.serializer(), ModelType.TRANSLATION)
```
### After (New Architecture):
```kotlin
val template = MyApiRequest(param)
apiRequestHandler.executeRequest(template)
```
## Common Patterns
### 1. Language-Specific Requests
```kotlin
class LanguageSpecificRequest(
private val text: String,
private val sourceLanguage: String,
private val targetLanguage: String
) : BaseApiRequestTemplate<TranslationResponse>() {
override val serviceName = "TranslationService"
init {
promptBuilder.basePrompt = "Translate from $sourceLanguage to $targetLanguage: '$text'"
withJsonResponse("a JSON object with 'translatedText' containing the translation")
}
}
```
### 2. Batch Processing Requests
```kotlin
class BatchProcessingRequest(
private val items: List<String>,
private val operation: String
) : BaseApiRequestTemplate<BatchResponse>() {
override val serviceName = "BatchService"
init {
val itemsJson = Json.encodeToString(items)
promptBuilder.basePrompt = "Perform $operation on these items: $itemsJson"
withJsonResponse("a JSON object with 'results' array containing processed items")
}
}
```
### 3. Configuration-Based Requests
```kotlin
class ConfigurableRequest(
private val baseText: String,
private val customInstructions: String,
private val outputFormat: String
) : BaseApiRequestTemplate<ConfigurableResponse>() {
override val serviceName = "ConfigurableService"
init {
promptBuilder.basePrompt = baseText
addDetail(customInstructions)
withJsonResponse(outputFormat)
}
}
```
## Troubleshooting
### Common Issues and Solutions
1. **"Unresolved reference" errors**
- Make sure you're calling the correct method (private methods stay within their class)
- Check imports are correct
2. **JSON parsing failures**
- Use `jsonHelper.validateRequiredFields()` to check structure first
- Check that your response data class matches the actual JSON structure
- Look at logs for detailed parsing error messages
3. **Template not working**
- Verify all required fields are listed in `requiredFields`
- Check that the prompt clearly explains the expected JSON format
- Ensure the serializer matches your data class
4. **API request timeouts**
- Check network connectivity
- Verify the API manager is properly initialized
- Look at API logs for detailed error information
## Best Practices Summary
1. **Always use templates** for new API requests
2. **Define required fields** to catch malformed responses early
3. **Log comprehensively** at appropriate levels
4. **Handle errors gracefully** with proper Result types
5. **Write tests** for both templates and parsing logic
6. **Keep prompts clear** and specific about JSON structure
7. **Use descriptive service names** for better logging
8. **Validate inputs** before making API requests
## Future Considerations
When extending the architecture:
1. **Reuse existing templates** when possible
2. **Follow naming conventions** consistently
3. **Document custom logic** in comments
4. **Add comprehensive tests** for new functionality
5. **Consider backward compatibility** when changing existing APIs
This architecture is designed to be easily extensible while maintaining type safety and consistency across all API operations.