# USEE Adapters Guide

## Specification of JSON and HTTP Adapters

**Version 1.0**

> **Note on vocabulary:** USEE is a Spanish-originated protocol. File names, field names, CLI arguments, endpoint routes, and HTTP header values shown in code blocks (`ejecutar-json`, `ejecutar-http`, `nombre`, `--ayuda`, `/salud`, `/ayuda`, `X-USEE-Pieza`, `si`, `no`, etc.) are part of the normative standard and must be used as-is, the same way HTML tags like `<div>` are not translated. The surrounding prose is in English; the identifiers are not.

---

## Purpose

Adapters are translators. They let USEE pieces — designed to communicate via plain text and stdin/stdout — be integrated into contexts that expect other formats or protocols.

### The Communication Hierarchy

```
Level 1 (MANDATORY): stdin/stdout with USEE Text Format
         ↓
Level 2 (RECOMMENDED): stdout/stderr separation
         ↓
Level 3 (OPTIONAL): JSON adapter
         ↓
Level 4 (OPTIONAL): HTTP adapter
```

### Fundamental Principle

> Adapters do not add functionality. They only translate.

A piece with a JSON adapter does exactly the same thing as without it. The only difference is the input and output format.

---

## JSON Adapter (Level 3)

### Purpose

Lets applications that work with JSON communicate with USEE pieces without needing to implement an FTU parser.

### File

```
ejecutar-json
```

### Behavior

```
                    ┌─────────────────┐
   Input JSON    →  │ ejecutar-json   │  →   Output JSON
                    │                 │
                    │  ┌───────────┐  │
                    │  │ Translate │  │
                    │  │  to FTU   │  │
                    │  └─────┬─────┘  │
                    │        ↓        │
                    │  ┌───────────┐  │
                    │  │ ejecutar  │  │
                    │  │  (piece)  │  │
                    │  └─────┬─────┘  │
                    │        ↓        │
                    │  ┌───────────┐  │
                    │  │ Translate │  │
                    │  │  to JSON  │  │
                    │  └───────────┘  │
                    └─────────────────┘
```

The JSON adapter is a wrapper that:

1. Receives JSON on stdin
2. Translates it to FTU
3. Runs the piece with the FTU
4. Translates the FTU output back to JSON
5. Emits JSON on stdout

---

### JSON → FTU Translation Rules

#### Simple Values

| JSON | FTU |
|------|-----|
| `"texto"` | `texto` |
| `123` | `123` |
| `45.67` | `45.67` |
| `true` | `si` |
| `false` | `no` |
| `null` | (empty value) |

#### Objects

```json
{
  "nombre": "Juan",
  "edad": 30
}
```

```
nombre: Juan
edad: 30
```

#### Nested Objects

```json
{
  "usuario": {
    "nombre": "Juan",
    "direccion": {
      "ciudad": "México",
      "pais": "MX"
    }
  }
}
```

```
usuario.nombre: Juan
usuario.direccion.ciudad: México
usuario.direccion.pais: MX
```

#### Arrays of Primitives

```json
{
  "roles": ["admin", "editor", "lector"]
}
```

```
roles: admin, editor, lector
```

#### Arrays of Objects

```json
{
  "usuarios": [
    {"nombre": "Juan", "rol": "admin"},
    {"nombre": "María", "rol": "editor"}
  ]
}
```

**Option A: Separated records (preferred for homogeneous lists)**

```
nombre: Juan
rol: admin
---
nombre: María
rol: editor
```

**Option B: Numeric indices (for lists embedded within other data)**

```
usuarios.0.nombre: Juan
usuarios.0.rol: admin
usuarios.1.nombre: María
usuarios.1.rol: editor
```

#### Mixed Arrays

```json
{
  "datos": [1, "texto", true]
}
```

```
datos: 1, texto, si
```

---

### FTU → JSON Translation Rules

#### Type Detection

The adapter must infer types from FTU values:

| FTU Value | JSON Type | Example |
|-----------|-----------|---------|
| Digits only | integer | `30` → `30` |
| Digits with a period | decimal | `3.14` → `3.14` |
| `si` | boolean true | `si` → `true` |
| `no` | boolean false | `no` → `false` |
| Empty | null | `` → `null` |
| Contains `, ` | array | `a, b` → `["a", "b"]` |
| Dot in key | nested object | `a.b: x` → `{"a":{"b":"x"}}` |
| Other | string | `Juan` → `"Juan"` |

#### Multiple Records

```
nombre: Juan
edad: 30
---
nombre: María
edad: 25
```

```json
[
  {"nombre": "Juan", "edad": 30},
  {"nombre": "María", "edad": 25}
]
```

#### Single Record

```
nombre: Juan
edad: 30
```

```json
{
  "nombre": "Juan",
  "edad": 30
}
```

**Note:** A single record produces an object, not a one-element array.

---

### JSON Adapter Implementation

#### Pseudocode

```
run_json():
    # Read JSON from stdin
    json_input = read_stdin()
    
    # Validate JSON
    if not is_valid_json(json_input):
        json_error = {
            "estado": "error",
            "codigo": "json_invalido",
            "mensaje": "Input is not valid JSON"
        }
        write_stderr(json_to_text(json_error))
        exit(2)
    
    # Translate to FTU
    obj = parse_json(json_input)
    ftu_input = object_to_ftu(obj)
    
    # Run the piece
    result = run_process("./ejecutar", stdin=ftu_input)
    
    # Translate output to JSON
    if result.code == 0:
        ftu_output = result.stdout
    else:
        ftu_output = result.stderr
    
    records = parse_ftu(ftu_output)
    
    if length(records) == 1:
        json_output = record_to_json(records[0])
    else:
        json_output = [record_to_json(r) for r in records]
    
    # Emit result
    if result.code == 0:
        write_stdout(json_to_text(json_output))
    else:
        write_stderr(json_to_text(json_output))
    
    exit(result.code)
```

#### Translation Functions

```
object_to_ftu(obj, prefix=""):
    lines = []
    
    for each (key, value) in obj:
        full_key = prefix + key if prefix else key
        
        if value is object:
            lines.append(object_to_ftu(value, full_key + "."))
        else if value is array:
            if all_primitives(value):
                lines.append(full_key + ": " + value.join(", "))
            else:
                for i, element in enumerate(value):
                    lines.append(object_to_ftu(element, full_key + "." + i + "."))
        else if value is boolean:
            lines.append(full_key + ": " + ("si" if value else "no"))
        else if value is null:
            lines.append(full_key + ":")
        else:
            lines.append(full_key + ": " + text(value))
    
    return lines.join("\n")

record_to_json(record):
    result = {}
    
    for each (key, value) in record:
        parts = key.split(".")
        current = result
        
        for i, part in enumerate(parts[:-1]):
            if part not in current:
                current[part] = {}
            current = current[part]
        
        final_key = parts[-1]
        current[final_key] = infer_type(value)
    
    return result

infer_type(value):
    if value is empty:
        return null
    if value == "si":
        return true
    if value == "no":
        return false
    if is_integer(value):
        return integer(value)
    if is_decimal(value):
        return decimal(value)
    if value.contains(", "):
        return [infer_type(v.trim()) for v in value.split(", ")]
    return value
```

---

### Using the JSON Adapter

#### Command Line

```bash
# Direct JSON input
echo '{"usuario": "john@example.com", "clave": "secret123"}' | ./ejecutar-json

# From a file
./ejecutar-json < input.json > output.json

# With jq for formatting
echo '{"usuario": "john@example.com", "clave": "secret123"}' | ./ejecutar-json | jq .
```

#### Successful Output

```json
{
  "estado": "ok",
  "sesion_id": "ses_abc123",
  "expira": "2025-01-16T10:30:00Z",
  "usuario": {
    "id": "usr_001",
    "nombre": "Juan Pérez"
  }
}
```

#### Error Output

```json
{
  "estado": "error",
  "codigo": "credenciales_invalidas",
  "mensaje": "The email or password is incorrect",
  "sugerencia": "Check your credentials and try again"
}
```

---

### JSON Adapter Arguments

| Argument | Description |
|-----------|-------------|
| `--ayuda` | Shows usage instructions |
| `--version` | Shows the piece version |
| `--compacto` | Unformatted JSON output (single line) |
| `--pretty` | Pretty-printed JSON output with indentation (default) |

---

## HTTP Adapter (Level 4)

### Purpose

Exposes the piece as a network service reachable via HTTP requests. This enables use from web and mobile applications, or any HTTP client.

### File

```
ejecutar-http
```

### Behavior

```
                         ┌─────────────────────┐
   HTTP Request     →    │   ejecutar-http     │    →    HTTP Response
                         │                     │
                         │  ┌───────────────┐  │
                         │  │  HTTP Server  │  │
                         │  └───────┬───────┘  │
                         │          ↓          │
                         │  ┌───────────────┐  │
                         │  │ Translate     │  │
                         │  │ body to FTU   │  │
                         │  └───────┬───────┘  │
                         │          ↓          │
                         │  ┌───────────────┐  │
                         │  │   ejecutar    │  │
                         │  │   (piece)     │  │
                         │  └───────┬───────┘  │
                         │          ↓          │
                         │  ┌───────────────┐  │
                         │  │ Translate to  │  │
                         │  │   response    │  │
                         │  └───────────────┘  │
                         └─────────────────────┘
```

---

### Standard Endpoints

| Method | Route | Description |
|--------|------|-------------|
| `POST` | `/` | Runs the piece |
| `GET` | `/salud` | Verifies the service is up |
| `GET` | `/version` | Returns piece information |
| `GET` | `/ayuda` | Returns documentation |
| `GET` | `/metricas` | Returns usage metrics (optional) |

---

### POST / (Run the Piece)

#### Accepted Input Formats

The HTTP adapter accepts multiple input formats depending on the `Content-Type` header:

| Content-Type | Format |
|--------------|---------|
| `text/plain` | USEE Text Format |
| `application/json` | JSON |
| `application/x-www-form-urlencoded` | URL-encoded form |
| `multipart/form-data` | Multipart form |

#### FTU Example

```bash
curl -X POST http://localhost:8080/ \
  -H "Content-Type: text/plain" \
  -d "usuario: john@example.com
clave: secret123"
```

#### JSON Example

```bash
curl -X POST http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -d '{"usuario": "john@example.com", "clave": "secret123"}'
```

#### Form Example

```bash
curl -X POST http://localhost:8080/ \
  -d "usuario=john@example.com" \
  -d "clave=secret123"
```

#### Response Format

The response format depends on the `Accept` header:

| Accept | Response Format |
|--------|---------------------|
| `text/plain` | USEE Text Format |
| `application/json` | JSON (default) |
| `*/*` | JSON |

---

### GET /salud

Verifies the service is running.

#### Request

```bash
curl http://localhost:8080/salud
```

#### Successful Response

```json
{
  "estado": "ok",
  "timestamp": "2025-01-15T10:30:00Z"
}
```

**HTTP code:** `200 OK`

#### Degraded Response

```json
{
  "estado": "degradado",
  "timestamp": "2025-01-15T10:30:00Z",
  "mensaje": "Database experiencing high latency"
}
```

**HTTP code:** `200 OK` (the service responds, despite problems)

#### Service Unavailable

**HTTP code:** `503 Service Unavailable`

---

### GET /version

Returns information about the piece.

#### Request

```bash
curl http://localhost:8080/version
```

#### Response

```json
{
  "nombre": "login",
  "version": "1.0.0",
  "protocolo": "usee-1.0",
  "adaptador": "http-1.0"
}
```

---

### GET /ayuda

Returns usage documentation.

#### Request

```bash
curl http://localhost:8080/ayuda
```

#### Response

```json
{
  "nombre": "login",
  "descripcion": "Authenticates a user with email and password",
  "entrada": {
    "campos_obligatorios": [
      {
        "nombre": "usuario",
        "tipo": "texto",
        "descripcion": "User email address"
      },
      {
        "nombre": "clave",
        "tipo": "texto",
        "descripcion": "User password"
      }
    ],
    "campos_opcionales": [
      {
        "nombre": "recordar",
        "tipo": "booleano",
        "default": "no",
        "descripcion": "Extend session duration"
      }
    ]
  },
  "salida": {
    "exitosa": [
      {"nombre": "estado", "tipo": "texto", "valor": "ok"},
      {"nombre": "sesion_id", "tipo": "texto"},
      {"nombre": "expira", "tipo": "fecha"}
    ],
    "error": [
      {"nombre": "estado", "tipo": "texto", "valor": "error"},
      {"nombre": "codigo", "tipo": "texto"},
      {"nombre": "mensaje", "tipo": "texto"}
    ]
  },
  "ejemplo": {
    "entrada": {"usuario": "example@email.com", "clave": "password"},
    "salida": {"estado": "ok", "sesion_id": "ses_xxx", "expira": "2025-01-16T10:30:00Z"}
  }
}
```

---

### GET /metricas (Optional)

Returns usage metrics for the service.

#### Request

```bash
curl http://localhost:8080/metricas
```

#### Response

```json
{
  "desde": "2025-01-15T00:00:00Z",
  "solicitudes_totales": 15234,
  "solicitudes_exitosas": 14892,
  "solicitudes_error": 342,
  "tiempo_respuesta_promedio_ms": 45,
  "tiempo_respuesta_p95_ms": 120,
  "tiempo_respuesta_p99_ms": 250
}
```

---

### HTTP Codes

| Code | When Used |
|--------|---------------|
| `200 OK` | Successful operation |
| `400 Bad Request` | Invalid or malformed input |
| `401 Unauthorized` | Authentication required (if applicable) |
| `404 Not Found` | Route not found |
| `405 Method Not Allowed` | HTTP method not supported |
| `415 Unsupported Media Type` | Content-Type not supported |
| `422 Unprocessable Entity` | Valid input but with logic errors |
| `429 Too Many Requests` | Rate limit exceeded |
| `500 Internal Server Error` | Internal piece error |
| `503 Service Unavailable` | Service unavailable |

### Mapping Piece Exit Codes to HTTP

| Piece Exit Code | HTTP Code |
|------------------------|-------------|
| 0 | 200 OK |
| 1 | 422 Unprocessable Entity |
| 2 | 400 Bad Request |
| 3 | 500 Internal Server Error |
| 4 | 503 Service Unavailable |
| 5 | 503 Service Unavailable |
| 10-99 | 422 Unprocessable Entity |

---

### Response Headers

| Header | Value | Description |
|--------|-------|-------------|
| `Content-Type` | `application/json` or `text/plain` | Response format |
| `X-USEE-Pieza` | `login` | Piece name |
| `X-USEE-Version` | `1.0.0` | Piece version |
| `X-USEE-Tiempo-Ms` | `45` | Processing time |
| `X-Request-Id` | `req_abc123` | Unique request ID |

---

### HTTP Adapter Arguments

| Argument | Description | Default |
|-----------|-------------|---------|
| `--puerto=N` | Port to listen on | 8080 |
| `--host=HOST` | Host to listen on | 0.0.0.0 |
| `--timeout=S` | Request timeout in seconds | 30 |
| `--max-body=BYTES` | Maximum body size | 1048576 (1MB) |
| `--cors` | Enable CORS for every origin | disabled |
| `--cors-origen=URL` | Enable CORS for a specific origin | - |
| `--log=LEVEL` | Logging level (debug, info, error) | info |
| `--metricas` | Enable the /metricas endpoint | disabled |

### Startup Examples

```bash
# Start on the default port
./ejecutar-http

# Custom port
./ejecutar-http --puerto=3000

# With CORS enabled
./ejecutar-http --cors

# With detailed logging
./ejecutar-http --log=debug

# Typical production
./ejecutar-http --puerto=8080 --timeout=10 --metricas
```

---

### HTTP Adapter Implementation

#### Pseudocode

```
run_http(args):
    port = args.puerto or 8080
    host = args.host or "0.0.0.0"
    
    server = create_http_server(host, port)
    
    server.register("POST", "/", handle_execution)
    server.register("GET", "/salud", handle_health)
    server.register("GET", "/version", handle_version)
    server.register("GET", "/ayuda", handle_help)
    
    if args.metricas:
        server.register("GET", "/metricas", handle_metrics)
    
    log("Server started on " + host + ":" + port)
    server.start()

handle_execution(request, response):
    start = current_time()
    request_id = generate_id()
    
    # Determine input format
    content_type = request.header("Content-Type")
    
    if content_type == "application/json":
        ftu_input = json_to_ftu(request.body)
    else if content_type == "text/plain":
        ftu_input = request.body
    else if content_type == "application/x-www-form-urlencoded":
        ftu_input = form_to_ftu(request.body)
    else:
        response.status(415)
        response.json({"estado": "error", "codigo": "content_type_no_soportado"})
        return
    
    # Run the piece
    result = run_process("./ejecutar", stdin=ftu_input, timeout=30)
    
    # Determine output format
    accept = request.header("Accept") or "application/json"
    
    if result.code == 0:
        output = result.stdout
        http_status = 200
    else:
        output = result.stderr
        http_status = map_http_code(result.code)
    
    # Prepare response
    if accept.contains("application/json"):
        response.header("Content-Type", "application/json")
        body = ftu_to_json(output)
    else:
        response.header("Content-Type", "text/plain")
        body = output
    
    # USEE headers
    response.header("X-USEE-Pieza", PIECE_NAME)
    response.header("X-USEE-Version", PIECE_VERSION)
    response.header("X-USEE-Tiempo-Ms", current_time() - start)
    response.header("X-Request-Id", request_id)
    
    response.status(http_status)
    response.send(body)
    
    # Record metrics
    record_metric(request_id, http_status, current_time() - start)

handle_health(request, response):
    response.json({
        "estado": "ok",
        "timestamp": iso_timestamp()
    })

handle_version(request, response):
    response.json({
        "nombre": PIECE_NAME,
        "version": PIECE_VERSION,
        "protocolo": "usee-1.0",
        "adaptador": "http-1.0"
    })
```

---

## Security Considerations

### For the JSON Adapter

| Risk | Mitigation |
|--------|------------|
| Malformed JSON | Validate before processing |
| Excessively large input | Limit stdin size |
| Injection in values | The piece must sanitize its inputs |

### For the HTTP Adapter

| Risk | Mitigation |
|--------|------------|
| DoS attacks | Rate-limit requests |
| Excessively large bodies | `--max-body` limits size |
| Timeouts | `--timeout` cancels long requests |
| Insecure CORS | Use a specific `--cors-origen` in production |
| Header injection | Validate and sanitize input |

### Production Recommendations

```bash
# DO NOT do in production
./ejecutar-http --cors  # CORS open to everyone

# Do in production
./ejecutar-http \
  --puerto=8080 \
  --timeout=10 \
  --max-body=102400 \
  --cors-origen=https://my-app.com \
  --log=error
```

---

## Adapter Configuration

Adapters can be configured via a `CONFIG.adaptadores.usee` file:

```
# Adapter configuration

json.pretty: si
json.inferir_tipos: si

http.puerto: 8080
http.host: 0.0.0.0
http.timeout: 30
http.max_body: 1048576
http.cors: no
http.cors_origen: 
http.log: info
http.metricas: no
```

Adapters read this file if it exists, but command-line arguments take priority.

---

## Adapter Tests

### Tests for the JSON Adapter

Create a `pruebas/json/` directory:

```
pruebas/json/
├── caso-basico.entrada.json
├── caso-basico.salida.json
├── caso-anidado.entrada.json
├── caso-anidado.salida.json
├── error-json-invalido.entrada.json
└── error-json-invalido.salida.json
```

**caso-basico.entrada.json:**
```json
{
  "usuario": "test@example.com",
  "clave": "test123"
}
```

**caso-basico.salida.json:**
```json
{
  "estado": "ok",
  "sesion_id": "*",
  "expira": "*",
  "usuario_id": "usr_test_001"
}
```

### Tests for the HTTP Adapter

Create a `pruebas/http/` directory:

```
pruebas/http/
├── caso-basico.solicitud
├── caso-basico.respuesta
├── caso-basico.status
├── salud.solicitud
├── salud.respuesta
└── salud.status
```

**caso-basico.solicitud:**
```
POST / HTTP/1.1
Content-Type: application/json

{"usuario": "test@example.com", "clave": "test123"}
```

**caso-basico.respuesta:**
```json
{
  "estado": "ok",
  "sesion_id": "*",
  "expira": "*"
}
```

**caso-basico.status:**
```
200
```

---

## Full Example: Login Piece with Adapters

### File Structure

```
login/
├── PIEZA.usee
├── LEEME.md
├── ENTRADA.ejemplo
├── SALIDA.ejemplo
├── ejecutar              # Level 1: FTU stdin/stdout
├── ejecutar-json         # Level 3: JSON stdin/stdout
├── ejecutar-http         # Level 4: HTTP server
├── CONFIG.adaptadores.usee
├── pruebas/
│   ├── caso-basico.entrada
│   ├── caso-basico.salida
│   └── ...
├── pruebas/json/
│   ├── caso-basico.entrada.json
│   ├── caso-basico.salida.json
│   └── ...
└── pruebas/http/
    ├── caso-basico.solicitud
    ├── caso-basico.respuesta
    └── ...
```

### Usage in Different Contexts

#### Shell Script

```bash
echo "usuario: john@example.com
clave: secret123" | ./ejecutar
```

#### Node.js Application

```javascript
const { spawn } = require('child_process');

const piece = spawn('./ejecutar-json');
piece.stdin.write(JSON.stringify({
  usuario: 'john@example.com',
  clave: 'secret123'
}));
piece.stdin.end();

piece.stdout.on('data', (data) => {
  const result = JSON.parse(data);
  console.log(result);
});
```

#### Web Application (Frontend)

```javascript
const response = await fetch('http://localhost:8080/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    usuario: 'john@example.com',
    clave: 'secret123'
  })
});

const result = await response.json();
console.log(result);
```

#### cURL from the Terminal

```bash
# With JSON
curl -X POST http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -d '{"usuario": "john@example.com", "clave": "secret123"}'

# With FTU
curl -X POST http://localhost:8080/ \
  -H "Content-Type: text/plain" \
  -d "usuario: john@example.com
clave: secret123"
```

---

## Adapter Verification

The USEE verifier includes additional checks for adapters:

### Optional Checks

If `ejecutar-json` exists:

- [ ] Processes valid JSON
- [ ] Rejects invalid JSON with an appropriate error
- [ ] The output is valid JSON
- [ ] The tests in `pruebas/json/` pass

If `ejecutar-http` exists:

- [ ] Starts without errors
- [ ] `/salud` responds with 200
- [ ] `/version` returns the correct version
- [ ] `POST /` processes requests
- [ ] The tests in `pruebas/http/` pass
- [ ] Shuts down cleanly on SIGTERM

---

## Summary

| Adapter | File | Purpose | When to Use |
|-----------|---------|-----------|-------------|
| **JSON** | `ejecutar-json` | Translate JSON ↔ FTU | Integration with apps that use JSON |
| **HTTP** | `ejecutar-http` | Expose as a web service | Web apps, mobile apps, APIs |

### The Golden Rule

> Adapters translate; they do not transform.

A piece must behave exactly the same regardless of which adapter is used. The only difference is the communication format.

---

**USEE Adapters**: Multiple doors, same room.
