Skip to content

Commit 2425b17

Browse files
authored
Merge pull request #35 from oproto/feature/lambda-merge-tool
feat(lambda-merge): add AWS Lambda merge tool with CDK infrastructure
2 parents fdc5997 + 0edc160 commit 2425b17

64 files changed

Lines changed: 9842 additions & 18 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Design Document: Dictionary Schema Support
2+
3+
## Overview
4+
5+
This design document describes the implementation of proper dictionary type handling in the Oproto.Lambda.OpenApi source generator. The solution adds dictionary detection logic to the type analysis pipeline and generates correct OpenAPI `additionalProperties` schemas for dictionary types.
6+
7+
### OpenAPI Dictionary Representation
8+
9+
Per the [OpenAPI Specification](https://swagger.io/docs/specification/data-models/dictionaries/), dictionaries (maps, hashmaps, associative arrays) are represented using `type: object` with `additionalProperties` defining the value type. This is the standard pattern recognized by all major code generators including:
10+
11+
- **Kiota** (Microsoft) - generates `IDictionary<string, T>` in C#
12+
- **OpenAPI Generator** - generates `Dictionary<string, T>` in C#
13+
- **NSwag** - generates `IDictionary<string, T>` in C#
14+
15+
OpenAPI only supports string keys for dictionaries, which aligns with JSON's object key constraints.
16+
17+
## Architecture
18+
19+
The implementation follows the existing partial class pattern used by the source generator, adding dictionary-specific logic to the type detection and schema creation pipeline.
20+
21+
```
22+
┌─────────────────────────────────────────────────────────────────┐
23+
│ CreateSchema (Entry Point) │
24+
└─────────────────────────────────────────────────────────────────┘
25+
26+
27+
┌─────────────────────────────────────────────────────────────────┐
28+
│ 1. TryCreateNullableSchema (existing) │
29+
│ 2. TryCreateSpecialTypeSchema (existing - Ulid) │
30+
│ 3. TryCreateCollectionSchema (existing - arrays/lists) │
31+
│ 4. TryCreateDictionarySchema (NEW) ◄─────────────────────────│
32+
│ 5. IsSimpleType → CreateSimpleTypeSchema (existing) │
33+
│ 6. CreateComplexTypeSchema (existing - fallback) │
34+
└─────────────────────────────────────────────────────────────────┘
35+
```
36+
37+
The key insight is that dictionary detection must occur:
38+
- After nullable handling (to unwrap `Nullable<T>`)
39+
- After collection handling (dictionaries are not arrays)
40+
- Before complex type handling (to prevent incorrect object schema generation)
41+
42+
## Components and Interfaces
43+
44+
### New Methods in OpenApiSpecGenerator_Types.cs
45+
46+
```csharp
47+
/// <summary>
48+
/// Determines if a type is a dictionary type (Dictionary, IDictionary, IReadOnlyDictionary).
49+
/// </summary>
50+
/// <param name="typeSymbol">The type to check</param>
51+
/// <param name="keyType">Output parameter for the dictionary's key type</param>
52+
/// <param name="valueType">Output parameter for the dictionary's value type</param>
53+
/// <returns>True if the type is a dictionary type</returns>
54+
private bool IsDictionaryType(ITypeSymbol typeSymbol, out ITypeSymbol keyType, out ITypeSymbol valueType)
55+
```
56+
57+
### New Methods in OpenApiSpecGenerator_Schema.cs
58+
59+
```csharp
60+
/// <summary>
61+
/// Attempts to create a schema for dictionary types.
62+
/// </summary>
63+
/// <param name="typeSymbol">The type symbol to check for dictionary</param>
64+
/// <param name="memberSymbol">The member symbol for additional metadata</param>
65+
/// <param name="schema">The output schema if the type is a dictionary</param>
66+
/// <returns>True if a dictionary schema was created, false otherwise</returns>
67+
private bool TryCreateDictionarySchema(ITypeSymbol typeSymbol, ISymbol memberSymbol, out OpenApiSchema schema)
68+
```
69+
70+
### Integration Point in CreateSchema
71+
72+
The `CreateSchema` method in `OpenApiSpecGenerator_Schema.cs` will be modified to call `TryCreateDictionarySchema` after collection handling but before complex type handling.
73+
74+
## Data Models
75+
76+
### Dictionary Type Detection Patterns
77+
78+
The following type patterns will be recognized as dictionaries:
79+
80+
| Type Pattern | MetadataName | Detection Method |
81+
|--------------|--------------|------------------|
82+
| `Dictionary<K,V>` | `Dictionary`2` | Direct type check |
83+
| `IDictionary<K,V>` | `IDictionary`2` | Direct type check |
84+
| `IReadOnlyDictionary<K,V>` | `IReadOnlyDictionary`2` | Direct type check |
85+
| Custom types implementing IDictionary | N/A | Interface check |
86+
87+
### Generated Schema Patterns
88+
89+
| Input Type | Generated Schema |
90+
|------------|------------------|
91+
| `Dictionary<string, string>` | `{ "type": "object", "additionalProperties": { "type": "string" } }` |
92+
| `Dictionary<string, int>` | `{ "type": "object", "additionalProperties": { "type": "integer" } }` |
93+
| `Dictionary<string, ComplexType>` | `{ "type": "object", "additionalProperties": { "$ref": "#/components/schemas/ComplexType" } }` |
94+
| `Dictionary<string, List<T>>` | `{ "type": "object", "additionalProperties": { "type": "array", "items": {...} } }` |
95+
| `Dictionary<string, Dictionary<string, T>>` | `{ "type": "object", "additionalProperties": { "type": "object", "additionalProperties": {...} } }` |
96+
97+
## Correctness Properties
98+
99+
*A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
100+
101+
### Property 1: Dictionary Type Detection
102+
103+
*For any* type symbol that is `Dictionary<K,V>`, `IDictionary<K,V>`, `IReadOnlyDictionary<K,V>`, or implements `IDictionary<K,V>`, the `IsDictionaryType` method SHALL return true and correctly extract the key and value types.
104+
105+
**Validates: Requirements 1.1, 1.2, 1.3, 1.4, 1.5**
106+
107+
### Property 2: Dictionary Schema Structure
108+
109+
*For any* dictionary type, the generated OpenAPI schema SHALL have `type: "object"` and a non-null `additionalProperties` schema (not empty `properties`).
110+
111+
**Validates: Requirements 5.2**
112+
113+
### Property 3: Simple Value Type Schema
114+
115+
*For any* dictionary with a simple value type (string, int, bool, decimal, DateTime, etc.), the `additionalProperties` schema SHALL have the correct OpenAPI type and format matching the value type.
116+
117+
**Validates: Requirements 2.1, 2.2, 2.3, 2.4, 2.5**
118+
119+
### Property 4: Complex Value Type Reference
120+
121+
*For any* dictionary with a complex (non-simple) value type, the `additionalProperties` schema SHALL contain a reference (`$ref`) to the value type's schema in components.
122+
123+
**Validates: Requirements 3.1**
124+
125+
### Property 5: Nullable Dictionary Handling
126+
127+
*For any* nullable dictionary type (either `Nullable<Dictionary<K,V>>` or dictionary property with nullable annotation), the generated schema SHALL have `nullable: true`.
128+
129+
**Validates: Requirements 4.1, 4.2**
130+
131+
## Error Handling
132+
133+
### Invalid Dictionary Types
134+
135+
- If a dictionary type has non-string keys, the generator will still produce an `additionalProperties` schema (OpenAPI only supports string keys for objects)
136+
- If the value type cannot be resolved, the generator falls back to `additionalProperties: { type: "object" }`
137+
138+
### Circular References
139+
140+
- Dictionary value types that reference the containing type are handled by the existing circular reference detection in `_processedTypes`
141+
- Self-referential dictionaries produce schemas with `$ref` to prevent infinite recursion
142+
143+
### Attribute Processing Errors
144+
145+
- Invalid JSON in `[OpenApiSchema(Example = "...")]` is handled gracefully with fallback to string representation
146+
- Missing or malformed attributes are ignored, using default schema generation
147+
148+
## Testing Strategy
149+
150+
### Unit Tests
151+
152+
Unit tests will verify specific examples and edge cases:
153+
154+
1. `Dictionary<string, string>` produces correct schema
155+
2. `Dictionary<string, int>` produces correct schema with integer type
156+
3. `Dictionary<string, ComplexType>` produces schema with $ref
157+
4. `Dictionary<string, List<string>>` produces nested array schema
158+
5. `Dictionary<string, Dictionary<string, int>>` produces nested dictionary schema
159+
6. Nullable dictionary produces schema with `nullable: true`
160+
7. Dictionary with `[OpenApiSchema]` attributes applies description and example
161+
8. Custom type implementing `IDictionary<K,V>` is detected as dictionary
162+
163+
### Property-Based Tests
164+
165+
Property-based tests will verify universal properties using the fast-check library pattern:
166+
167+
1. **Dictionary Detection Property**: For all generated dictionary type symbols, `IsDictionaryType` returns true
168+
2. **Schema Structure Property**: For all dictionary types, generated schema has `additionalProperties` (not empty `properties`)
169+
3. **Value Type Mapping Property**: For all dictionaries with simple value types, `additionalProperties.Type` matches the expected OpenAPI type
170+
4. **Complex Type Reference Property**: For all dictionaries with complex value types, `additionalProperties` contains a `$ref`
171+
5. **Nullable Property**: For all nullable dictionaries, schema has `nullable: true`
172+
173+
### Test Configuration
174+
175+
- Property tests will run minimum 100 iterations
176+
- Tests will use the existing `GenerateSchemaFromSource` helper pattern from `OpenApiGeneratorTests.cs`
177+
- Each property test will be tagged with: **Feature: dictionary-schema-support, Property N: {property_text}**
178+
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Requirements Document
2+
3+
## Introduction
4+
5+
This document specifies the requirements for adding proper dictionary type handling to the Oproto.Lambda.OpenApi source generator. Currently, dictionary types (`Dictionary<K,V>`, `IDictionary<K,V>`, etc.) are incorrectly treated as complex objects with empty properties, producing invalid OpenAPI schemas. This feature will enable the generator to produce correct `additionalProperties` schemas for dictionary types.
6+
7+
## Glossary
8+
9+
- **Source_Generator**: The Oproto.Lambda.OpenApi.SourceGenerator that analyzes C# code and produces OpenAPI specification files
10+
- **Dictionary_Type**: Any .NET type that implements `IDictionary<TKey, TValue>`, including `Dictionary<K,V>`, `IDictionary<K,V>`, and `IReadOnlyDictionary<K,V>`
11+
- **OpenAPI_Schema**: A JSON Schema-based structure that describes the shape of data in an OpenAPI specification
12+
- **Additional_Properties_Schema**: An OpenAPI schema pattern using `additionalProperties` to describe objects with dynamic string keys and typed values
13+
- **Value_Type**: The type of values stored in a dictionary (the `TValue` in `Dictionary<TKey, TValue>`)
14+
- **Simple_Type**: Primitive types (string, int, bool, etc.), enums, DateTime, DateOnly, TimeOnly, and Guid
15+
- **Complex_Type**: Non-primitive types that require a `$ref` reference in OpenAPI schemas
16+
17+
## Requirements
18+
19+
### Requirement 1: Dictionary Type Detection
20+
21+
**User Story:** As a developer using the source generator, I want dictionary types to be correctly identified, so that they are not incorrectly treated as complex objects.
22+
23+
#### Acceptance Criteria
24+
25+
1. WHEN the Source_Generator encounters a `Dictionary<TKey, TValue>` type, THE Source_Generator SHALL identify it as a Dictionary_Type
26+
2. WHEN the Source_Generator encounters an `IDictionary<TKey, TValue>` type, THE Source_Generator SHALL identify it as a Dictionary_Type
27+
3. WHEN the Source_Generator encounters an `IReadOnlyDictionary<TKey, TValue>` type, THE Source_Generator SHALL identify it as a Dictionary_Type
28+
4. WHEN the Source_Generator encounters a type implementing `IDictionary<TKey, TValue>`, THE Source_Generator SHALL identify it as a Dictionary_Type
29+
5. WHEN the Source_Generator identifies a Dictionary_Type, THE Source_Generator SHALL extract the Value_Type from the type arguments
30+
31+
### Requirement 2: Dictionary Schema Generation with Simple Value Types
32+
33+
**User Story:** As a developer, I want dictionaries with simple value types to generate correct OpenAPI schemas, so that API consumers understand the data structure.
34+
35+
#### Acceptance Criteria
36+
37+
1. WHEN the Source_Generator creates a schema for `Dictionary<string, string>`, THE Source_Generator SHALL produce a schema with `type: "object"` and `additionalProperties: { type: "string" }`
38+
2. WHEN the Source_Generator creates a schema for `Dictionary<string, int>`, THE Source_Generator SHALL produce a schema with `type: "object"` and `additionalProperties: { type: "integer" }`
39+
3. WHEN the Source_Generator creates a schema for `Dictionary<string, bool>`, THE Source_Generator SHALL produce a schema with `type: "object"` and `additionalProperties: { type: "boolean" }`
40+
4. WHEN the Source_Generator creates a schema for `Dictionary<string, decimal>`, THE Source_Generator SHALL produce a schema with `type: "object"` and `additionalProperties: { type: "number" }`
41+
5. WHEN the Source_Generator creates a schema for `Dictionary<string, DateTime>`, THE Source_Generator SHALL produce a schema with `type: "object"` and `additionalProperties: { type: "string", format: "date-time" }`
42+
43+
### Requirement 3: Dictionary Schema Generation with Complex Value Types
44+
45+
**User Story:** As a developer, I want dictionaries with complex value types to generate schemas with proper references, so that nested types are correctly documented.
46+
47+
#### Acceptance Criteria
48+
49+
1. WHEN the Source_Generator creates a schema for a dictionary with a Complex_Type value, THE Source_Generator SHALL produce a schema with `additionalProperties` containing a `$ref` to the value type
50+
2. WHEN the Source_Generator creates a schema for `Dictionary<string, List<T>>`, THE Source_Generator SHALL produce a schema with `additionalProperties` containing an array schema
51+
3. WHEN the Source_Generator creates a schema for `Dictionary<string, Dictionary<string, T>>`, THE Source_Generator SHALL produce a schema with nested `additionalProperties` schemas
52+
53+
### Requirement 4: Nullable Dictionary Handling
54+
55+
**User Story:** As a developer, I want nullable dictionaries to be correctly represented in the schema, so that optional dictionary properties are properly documented.
56+
57+
#### Acceptance Criteria
58+
59+
1. WHEN the Source_Generator creates a schema for `Dictionary<string, T>?` (nullable dictionary), THE Source_Generator SHALL produce a schema with `nullable: true`
60+
2. WHEN the Source_Generator creates a schema for a dictionary property with nullable annotation, THE Source_Generator SHALL set `nullable: true` on the schema
61+
62+
### Requirement 5: Dictionary Type Priority in Schema Creation
63+
64+
**User Story:** As a developer, I want dictionary detection to occur before complex type handling, so that dictionaries are not incorrectly processed as regular objects.
65+
66+
#### Acceptance Criteria
67+
68+
1. WHEN the Source_Generator processes a type, THE Source_Generator SHALL check for Dictionary_Type before checking for Complex_Type
69+
2. WHEN a Dictionary_Type is detected, THE Source_Generator SHALL NOT fall through to CreateComplexTypeSchema
70+
3. WHEN a type is both a Dictionary_Type and has other properties, THE Source_Generator SHALL treat it as a Dictionary_Type (additionalProperties takes precedence)
71+
72+
### Requirement 6: Schema Attribute Support for Dictionaries
73+
74+
**User Story:** As a developer, I want to apply OpenApiSchema attributes to dictionary properties, so that I can customize the generated schema.
75+
76+
#### Acceptance Criteria
77+
78+
1. WHEN a dictionary property has an `[OpenApiSchema]` attribute with Description, THE Source_Generator SHALL apply the description to the dictionary schema
79+
2. WHEN a dictionary property has an `[OpenApiSchema]` attribute with Example, THE Source_Generator SHALL apply the example to the dictionary schema

0 commit comments

Comments
 (0)