From d1e373b6f6d9d4849273f271426805a381403806 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 12:56:31 -0500 Subject: [PATCH 01/14] docs: Update How-To Guides category configuration with improved label and description Co-authored-by: aider --- website/docs/how-to-guides/_category_.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/website/docs/how-to-guides/_category_.json b/website/docs/how-to-guides/_category_.json index 69dca425..12e3146b 100644 --- a/website/docs/how-to-guides/_category_.json +++ b/website/docs/how-to-guides/_category_.json @@ -1,7 +1,8 @@ { - "label": "How-To Guide", + "label": "How-To Guides", "position": 3, "link": { - "type": "generated-index" + "type": "generated-index", + "description": "Learn how to use the library with practical examples and guides" } } From 61dc15e55b0db08c54412daaf502c2c2a603614c Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:02:22 -0500 Subject: [PATCH 02/14] docs: Update JSON parsing examples to use api-ts and io-ts codecs Co-authored-by: aider --- .../how-to-guides/json-parsing-examples.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 website/docs/how-to-guides/json-parsing-examples.md diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md new file mode 100644 index 00000000..495a6779 --- /dev/null +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -0,0 +1,51 @@ +# JSON Parsing with Codecs + +This guide demonstrates how to parse and validate JSON data using api-ts codecs. + +## Basic JSON Parsing + +Here's how to parse and validate JSON data using codecs: + +```typescript +import * as t from 'io-ts' +import { httpRequest } from '@api-ts/io-ts-http' + +// Define a codec for the expected structure +const UserCodec = t.type({ + name: t.string, + age: t.number +}) + +// Create an HTTP request codec that parses JSON +const UserRequest = httpRequest({ + body: UserCodec +}) + +// Usage examples +const validJson = '{"name": "Alice", "age": 30}' +const invalidJson = '{"name": "Bob"}' // Missing age field + +// Decode and validate +UserRequest.request.decode(validJson) +// Right({ name: "Alice", age: 30 }) + +UserRequest.request.decode(invalidJson) +// Left([{ value: { name: "Bob" }, message: "required property 'age'" }]) +``` + +## Date Transformations + +For handling dates in JSON: + +```typescript +import { DateFromISOString } from 'io-ts-types' + +const UserWithDateCodec = t.type({ + name: t.string, + birthDate: DateFromISOString +}) + +const input = '{"name": "Alice", "birthDate": "1990-01-01T00:00:00.000Z"}' +UserWithDateCodec.decode(JSON.parse(input)) +// Right({ name: "Alice", birthDate: Date(1990-01-01) }) +``` From 2847cc15b235e81e82ec929d77a121dba97bdad3 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:06:42 -0500 Subject: [PATCH 03/14] docs: Update JSON parsing guide with comprehensive examples and best practices Co-authored-by: aider --- .../how-to-guides/json-parsing-examples.md | 173 ++++++++++++++---- 1 file changed, 135 insertions(+), 38 deletions(-) diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md index 495a6779..3a457e91 100644 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -1,51 +1,148 @@ -# JSON Parsing with Codecs +# JSON Parsing and Type Validation -This guide demonstrates how to parse and validate JSON data using api-ts codecs. +Learn how to work with JSON data using TypeScript's type system and runtime validation. -## Basic JSON Parsing +## Parsing JSON from Strings -Here's how to parse and validate JSON data using codecs: +One common use case is parsing JSON from string input and validating its structure. Here's how you can create a type-safe parser: ```typescript -import * as t from 'io-ts' -import { httpRequest } from '@api-ts/io-ts-http' - -// Define a codec for the expected structure -const UserCodec = t.type({ - name: t.string, - age: t.number -}) - -// Create an HTTP request codec that parses JSON -const UserRequest = httpRequest({ - body: UserCodec -}) - -// Usage examples -const validJson = '{"name": "Alice", "age": 30}' -const invalidJson = '{"name": "Bob"}' // Missing age field - -// Decode and validate -UserRequest.request.decode(validJson) -// Right({ name: "Alice", age: 30 }) - -UserRequest.request.decode(invalidJson) -// Left([{ value: { name: "Bob" }, message: "required property 'age'" }]) +// Define the expected structure +interface User { + name: string; + age: number; +} + +// Type guard to validate the structure +function isUser(value: unknown): value is User { + return ( + typeof value === 'object' && + value !== null && + 'name' in value && + typeof (value as User).name === 'string' && + 'age' in value && + typeof (value as User).age === 'number' + ); +} + +// Parser that combines JSON parsing and validation +function parseUser(input: string): Result { + try { + const parsed = JSON.parse(input); + if (isUser(parsed)) { + return { + success: true, + value: parsed + }; + } + return { + success: false, + error: 'Invalid user structure' + }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Invalid JSON' + }; + } +} + +// Type for the result +interface Result { + success: boolean; + value?: T; + error?: string; +} + +// Usage examples: +const validJson = '{"name": "Alice", "age": 30}'; +const invalidJson = '{"name": "Bob"'; // Missing closing brace +const invalidStructure = '{"name": "Charlie"}'; // Missing age field + +console.log(parseUser(validJson)); +// { success: true, value: { name: "Alice", age: 30 } } + +console.log(parseUser(invalidJson)); +// { success: false, error: "Unexpected end of JSON input" } + +console.log(parseUser(invalidStructure)); +// { success: false, error: "Invalid user structure" } ``` -## Date Transformations +## Advanced Transformations -For handling dates in JSON: +You can create more complex parsers that handle data transformations: ```typescript -import { DateFromISOString } from 'io-ts-types' +interface UserWithDate { + name: string; + birthDate: Date; +} -const UserWithDateCodec = t.type({ - name: t.string, - birthDate: DateFromISOString -}) +function isISODateString(value: string): boolean { + const date = new Date(value); + return date instanceof Date && !isNaN(date.getTime()); +} -const input = '{"name": "Alice", "birthDate": "1990-01-01T00:00:00.000Z"}' -UserWithDateCodec.decode(JSON.parse(input)) -// Right({ name: "Alice", birthDate: Date(1990-01-01) }) +function parseUserWithDate(input: string): Result { + try { + const parsed = JSON.parse(input); + if ( + typeof parsed === 'object' && + parsed !== null && + typeof parsed.name === 'string' && + typeof parsed.birthDate === 'string' && + isISODateString(parsed.birthDate) + ) { + return { + success: true, + value: { + name: parsed.name, + birthDate: new Date(parsed.birthDate) + } + }; + } + return { + success: false, + error: 'Invalid user structure or date format' + }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Invalid JSON' + }; + } +} + +// Usage +const input = '{"name": "Alice", "birthDate": "1990-01-01T00:00:00.000Z"}'; +console.log(parseUserWithDate(input)); +// { success: true, value: { name: "Alice", birthDate: Date(1990-01-01) } } ``` + +## Error Handling + +The Result type provides a clean way to handle parsing and validation errors: + +```typescript +function processUser(input: string): string { + const result = parseUser(input); + if (result.success && result.value) { + return `Valid user: ${result.value.name}, ${result.value.age}`; + } + return `Validation failed: ${result.error}`; +} + +console.log(processUser('{"name": "Alice", "age": "30"}')); +// Validation failed: Invalid user structure +``` + +## Best Practices + +1. **Type Safety**: Use TypeScript interfaces to define expected data structures +2. **Type Guards**: Implement thorough type guards for runtime validation +3. **Error Handling**: Use a Result type to handle both success and error cases +4. **Transformation**: Keep parsing and transformation logic separate and composable +5. **Validation**: Include detailed validation messages for better debugging + +Remember that TypeScript's type system provides compile-time safety, while runtime validation ensures data integrity during execution. From a85e3914cc4cff603cbe041c63ba7165d6530f34 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:06:47 -0500 Subject: [PATCH 04/14] docs: Rewrite JSON parsing guide using api-ts codecs Co-authored-by: aider --- .../how-to-guides/json-parsing-examples.md | 174 ++++-------------- 1 file changed, 38 insertions(+), 136 deletions(-) diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md index 3a457e91..4061841e 100644 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -1,148 +1,50 @@ -# JSON Parsing and Type Validation +# JSON Parsing with api-ts -Learn how to work with JSON data using TypeScript's type system and runtime validation. +Learn how to parse and validate JSON data using api-ts codecs. -## Parsing JSON from Strings +## Basic Example -One common use case is parsing JSON from string input and validating its structure. Here's how you can create a type-safe parser: +Here's how to parse and validate JSON using api-ts: ```typescript -// Define the expected structure -interface User { - name: string; - age: number; -} - -// Type guard to validate the structure -function isUser(value: unknown): value is User { - return ( - typeof value === 'object' && - value !== null && - 'name' in value && - typeof (value as User).name === 'string' && - 'age' in value && - typeof (value as User).age === 'number' - ); -} - -// Parser that combines JSON parsing and validation -function parseUser(input: string): Result { - try { - const parsed = JSON.parse(input); - if (isUser(parsed)) { - return { - success: true, - value: parsed - }; - } - return { - success: false, - error: 'Invalid user structure' - }; - } catch (error) { - return { - success: false, - error: error instanceof Error ? error.message : 'Invalid JSON' - }; - } -} - -// Type for the result -interface Result { - success: boolean; - value?: T; - error?: string; -} - -// Usage examples: -const validJson = '{"name": "Alice", "age": 30}'; -const invalidJson = '{"name": "Bob"'; // Missing closing brace -const invalidStructure = '{"name": "Charlie"}'; // Missing age field - -console.log(parseUser(validJson)); -// { success: true, value: { name: "Alice", age: 30 } } - -console.log(parseUser(invalidJson)); -// { success: false, error: "Unexpected end of JSON input" } - -console.log(parseUser(invalidStructure)); -// { success: false, error: "Invalid user structure" } -``` - -## Advanced Transformations - -You can create more complex parsers that handle data transformations: - -```typescript -interface UserWithDate { - name: string; - birthDate: Date; -} - -function isISODateString(value: string): boolean { - const date = new Date(value); - return date instanceof Date && !isNaN(date.getTime()); -} - -function parseUserWithDate(input: string): Result { - try { - const parsed = JSON.parse(input); - if ( - typeof parsed === 'object' && - parsed !== null && - typeof parsed.name === 'string' && - typeof parsed.birthDate === 'string' && - isISODateString(parsed.birthDate) - ) { - return { - success: true, - value: { - name: parsed.name, - birthDate: new Date(parsed.birthDate) - } - }; - } - return { - success: false, - error: 'Invalid user structure or date format' - }; - } catch (error) { - return { - success: false, - error: error instanceof Error ? error.message : 'Invalid JSON' - }; - } -} - -// Usage -const input = '{"name": "Alice", "birthDate": "1990-01-01T00:00:00.000Z"}'; -console.log(parseUserWithDate(input)); -// { success: true, value: { name: "Alice", birthDate: Date(1990-01-01) } } +import * as t from 'io-ts' +import { httpRequest } from '@api-ts/io-ts-http' + +// Define the request structure +const UserRequest = httpRequest({ + body: t.type({ + name: t.string, + age: t.number + }) +}) + +// Parse and validate JSON +const validJson = '{"name": "Alice", "age": 30}' +const result = UserRequest.request.decode(validJson) +// Success: { name: "Alice", age: 30 } + +const invalidJson = '{"name": "Bob"}' // Missing age +UserRequest.request.decode(invalidJson) +// Error: required property 'age' ``` -## Error Handling +## Working with Dates -The Result type provides a clean way to handle parsing and validation errors: +For handling dates in JSON: ```typescript -function processUser(input: string): string { - const result = parseUser(input); - if (result.success && result.value) { - return `Valid user: ${result.value.name}, ${result.value.age}`; - } - return `Validation failed: ${result.error}`; -} - -console.log(processUser('{"name": "Alice", "age": "30"}')); -// Validation failed: Invalid user structure +import { DateFromISOString } from 'io-ts-types' + +const UserWithDate = httpRequest({ + body: t.type({ + name: t.string, + birthDate: DateFromISOString + }) +}) + +const input = '{"name": "Alice", "birthDate": "1990-01-01T00:00:00.000Z"}' +UserWithDate.request.decode(input) +// Success: { name: "Alice", birthDate: Date(1990-01-01) } ``` -## Best Practices - -1. **Type Safety**: Use TypeScript interfaces to define expected data structures -2. **Type Guards**: Implement thorough type guards for runtime validation -3. **Error Handling**: Use a Result type to handle both success and error cases -4. **Transformation**: Keep parsing and transformation logic separate and composable -5. **Validation**: Include detailed validation messages for better debugging - -Remember that TypeScript's type system provides compile-time safety, while runtime validation ensures data integrity during execution. +The library handles JSON parsing and validation in one step, providing type-safe results. From 9b0623f383a7cd59ce5f62fe33cafbaf7a90094d Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:13:10 -0500 Subject: [PATCH 05/14] docs: Simplify JSON parsing example introduction text Co-authored-by: aider --- website/docs/how-to-guides/json-parsing-examples.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md index 4061841e..0f03fe65 100644 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -4,7 +4,7 @@ Learn how to parse and validate JSON data using api-ts codecs. ## Basic Example -Here's how to parse and validate JSON using api-ts: +Parse and validate JSON using api-ts: ```typescript import * as t from 'io-ts' From 01821c50c6196ffe8898e8048cb45e68f6340011 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:13:14 -0500 Subject: [PATCH 06/14] docs: Add comprehensive JSON parsing examples with edge cases and complex type validation Co-authored-by: aider --- .../how-to-guides/json-parsing-examples.md | 98 +++++++++++++------ 1 file changed, 70 insertions(+), 28 deletions(-) diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md index 0f03fe65..32935f1b 100644 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -2,49 +2,91 @@ Learn how to parse and validate JSON data using api-ts codecs. -## Basic Example - -Parse and validate JSON using api-ts: +## Basic Request Validation ```typescript import * as t from 'io-ts' import { httpRequest } from '@api-ts/io-ts-http' -// Define the request structure -const UserRequest = httpRequest({ +// Define a request with query parameters and body +const SearchRequest = httpRequest({ + query: t.type({ + limit: t.number, + offset: t.number + }), body: t.type({ - name: t.string, - age: t.number + searchTerm: t.string, + filters: t.array(t.string) }) }) -// Parse and validate JSON -const validJson = '{"name": "Alice", "age": 30}' -const result = UserRequest.request.decode(validJson) -// Success: { name: "Alice", age: 30 } - -const invalidJson = '{"name": "Bob"}' // Missing age -UserRequest.request.decode(invalidJson) -// Error: required property 'age' +// Validate complete request +const validRequest = { + query: { limit: 10, offset: 0 }, + body: { + searchTerm: "api", + filters: ["active", "published"] + } +} +SearchRequest.request.decode(validRequest) +// Success: { query: { limit: 10, offset: 0 }, body: { searchTerm: "api", filters: ["active", "published"] } } ``` -## Working with Dates - -For handling dates in JSON: +## Edge Cases and Complex Types ```typescript -import { DateFromISOString } from 'io-ts-types' +import { Method } from '@api-ts/io-ts-http' -const UserWithDate = httpRequest({ - body: t.type({ - name: t.string, - birthDate: DateFromISOString - }) +// Union types for status codes +const StatusResponse = t.union([ + t.type({ status: t.literal(200), data: t.string }), + t.type({ status: t.literal(404), error: t.string }), + t.type({ status: t.literal(500), code: t.number, message: t.string }) +]) + +// Request with optional fields and specific HTTP method +const ApiRequest = httpRequest({ + method: Method.enum.POST, + headers: t.partial({ + 'x-api-version': t.string, + 'x-client-id': t.string + }), + query: t.partial({ + debug: t.boolean + }), + body: t.intersection([ + t.type({ required: t.string }), + t.partial({ optional: t.array(t.number) }) + ]) }) -const input = '{"name": "Alice", "birthDate": "1990-01-01T00:00:00.000Z"}' -UserWithDate.request.decode(input) -// Success: { name: "Alice", birthDate: Date(1990-01-01) } +// Examples of edge cases +const partialRequest = { + headers: { 'x-api-version': '2.0' }, + body: { required: 'test' } +} +ApiRequest.request.decode(partialRequest) +// Success: Accepts partial headers and query + +const invalidMethod = { + method: 'GET', // Wrong method + body: { required: 'test' } +} +ApiRequest.request.decode(invalidMethod) +// Error: Invalid method, expected POST + +const complexResponse = { + status: 500, + code: 5001, + message: 'Internal server error' +} +StatusResponse.decode(complexResponse) +// Success: Matches 500 status union case ``` -The library handles JSON parsing and validation in one step, providing type-safe results. +The library provides powerful type validation with: +- Union and intersection types +- Optional fields with `t.partial` +- Literal types for exact matches +- Method constraints +- Nested object validation From df71be73d7d688a65d99766d7a6bae66a013d794 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:25:12 -0500 Subject: [PATCH 07/14] docs: Update JSON parsing guide title and remove library details Co-authored-by: aider --- website/docs/how-to-guides/json-parsing-examples.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md index 32935f1b..2c8e8344 100644 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -1,6 +1,4 @@ -# JSON Parsing with api-ts - -Learn how to parse and validate JSON data using api-ts codecs. +# How to Parse JSON Data ## Basic Request Validation @@ -83,10 +81,3 @@ const complexResponse = { StatusResponse.decode(complexResponse) // Success: Matches 500 status union case ``` - -The library provides powerful type validation with: -- Union and intersection types -- Optional fields with `t.partial` -- Literal types for exact matches -- Method constraints -- Nested object validation From bfd378780ae75e67e4006bc71ae6df7f3d1b4f23 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:25:15 -0500 Subject: [PATCH 08/14] feat: Update JSON parsing documentation with comprehensive examples from compliance-tx-monitoring project Co-authored-by: aider --- .../how-to-guides/json-parsing-examples.md | 204 +++++++++++++----- 1 file changed, 148 insertions(+), 56 deletions(-) diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md index 2c8e8344..5313ad0d 100644 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -1,83 +1,175 @@ # How to Parse JSON Data -## Basic Request Validation +This guide shows common patterns for parsing and validating JSON data using api-ts. + +## Basic API Route Definition ```typescript import * as t from 'io-ts' import { httpRequest } from '@api-ts/io-ts-http' -// Define a request with query parameters and body -const SearchRequest = httpRequest({ - query: t.type({ - limit: t.number, - offset: t.number - }), - body: t.type({ - searchTerm: t.string, - filters: t.array(t.string) - }) +// Define reusable types +const Pagination = t.type({ + limit: t.number, + offset: t.number }) -// Validate complete request -const validRequest = { - query: { limit: 10, offset: 0 }, - body: { - searchTerm: "api", - filters: ["active", "published"] - } +const TimeRange = t.type({ + startTime: t.string, // ISO date string + endTime: t.string +}) + +// Define an API route with query parameters +const ListTransactions = httpRequest({ + method: 'GET', + query: t.intersection([ + Pagination, + TimeRange, + t.partial({ + status: t.union([ + t.literal('pending'), + t.literal('completed'), + t.literal('failed') + ]) + }) + ]) +}) + +// Usage example +const validQuery = { + limit: 20, + offset: 0, + startTime: '2024-01-01T00:00:00Z', + endTime: '2024-01-31T23:59:59Z', + status: 'pending' } -SearchRequest.request.decode(validRequest) -// Success: { query: { limit: 10, offset: 0 }, body: { searchTerm: "api", filters: ["active", "published"] } } +ListTransactions.request.decode({ query: validQuery }) +// Success: Validates query parameters ``` -## Edge Cases and Complex Types +## Complex Request Bodies ```typescript import { Method } from '@api-ts/io-ts-http' -// Union types for status codes -const StatusResponse = t.union([ - t.type({ status: t.literal(200), data: t.string }), - t.type({ status: t.literal(404), error: t.string }), - t.type({ status: t.literal(500), code: t.number, message: t.string }) +// Define nested structures +const Address = t.type({ + street: t.string, + city: t.string, + country: t.string, + postalCode: t.string +}) + +const CustomerInfo = t.intersection([ + t.type({ + id: t.string, + name: t.string, + email: t.string, + primaryAddress: Address + }), + t.partial({ + phone: t.string, + additionalAddresses: t.array(Address) + }) ]) -// Request with optional fields and specific HTTP method -const ApiRequest = httpRequest({ - method: Method.enum.POST, - headers: t.partial({ - 'x-api-version': t.string, - 'x-client-id': t.string +// Create request with complex body +const UpdateCustomer = httpRequest({ + method: Method.enum.PUT, + params: t.type({ + customerId: t.string }), - query: t.partial({ - debug: t.boolean + headers: t.type({ + 'x-request-id': t.string }), - body: t.intersection([ - t.type({ required: t.string }), - t.partial({ optional: t.array(t.number) }) - ]) + body: CustomerInfo }) -// Examples of edge cases -const partialRequest = { - headers: { 'x-api-version': '2.0' }, - body: { required: 'test' } +// Example usage with validation errors +const invalidRequest = { + params: { customerId: '123' }, + headers: { 'x-request-id': 'req-456' }, + body: { + id: '123', + name: 'John Doe', + email: 'invalid-email', // Will fail validation + primaryAddress: { + street: '123 Main St', + city: 'Boston', + // country missing - will fail validation + postalCode: '02101' + } + } } -ApiRequest.request.decode(partialRequest) -// Success: Accepts partial headers and query +UpdateCustomer.request.decode(invalidRequest) +// Error: Validation failures for email format and missing country +``` -const invalidMethod = { - method: 'GET', // Wrong method - body: { required: 'test' } -} -ApiRequest.request.decode(invalidMethod) -// Error: Invalid method, expected POST +## Response Handling + +```typescript +// Define response types with branded types for extra safety +const TransactionId = t.brand( + t.string, + (s): s is t.Branded => + /^tx-[a-f0-9]{24}$/.test(s), + 'TransactionId' +) + +const Transaction = t.type({ + id: TransactionId, + amount: t.number, + currency: t.string, + status: t.union([ + t.literal('pending'), + t.literal('completed'), + t.literal('failed') + ]), + metadata: t.record(t.string, t.unknown) +}) -const complexResponse = { - status: 500, - code: 5001, - message: 'Internal server error' +// Define success and error responses +const GetTransaction = httpRequest({ + method: 'GET', + params: t.type({ + transactionId: TransactionId + }), + response: { + 200: Transaction, + 404: t.type({ + error: t.literal('TransactionNotFound'), + message: t.string + }), + 500: t.type({ + error: t.literal('InternalServerError'), + code: t.number, + message: t.string + }) + } +}) + +// Example response handling +const response = { + status: 200, + body: { + id: 'tx-1234567890abcdef12345678', + amount: 100.50, + currency: 'USD', + status: 'completed', + metadata: { + source: 'web', + customer: 'cust_123' + } + } } -StatusResponse.decode(complexResponse) -// Success: Matches 500 status union case +GetTransaction.response[response.status].decode(response.body) +// Success: Validates response body against 200 status schema ``` + +The library provides: +- Type-safe request and response validation +- Composable type definitions +- Branded types for custom validation +- Union types for handling multiple response types +- Partial types for optional fields +- Intersection types for combining schemas From 1f4b1bffeccd989afbf0daa090eb324c07c16d0a Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:28:30 -0500 Subject: [PATCH 09/14] docs: Update JSON parsing examples with simplified, focused content Co-authored-by: aider --- .../how-to-guides/json-parsing-examples.md | 206 +++++------------- 1 file changed, 57 insertions(+), 149 deletions(-) diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md index 5313ad0d..501d20c8 100644 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -1,175 +1,83 @@ -# How to Parse JSON Data +# How to Parse JSON Data -This guide shows common patterns for parsing and validating JSON data using api-ts. - -## Basic API Route Definition +## Basic Request Validation ```typescript import * as t from 'io-ts' import { httpRequest } from '@api-ts/io-ts-http' -// Define reusable types -const Pagination = t.type({ - limit: t.number, - offset: t.number -}) - -const TimeRange = t.type({ - startTime: t.string, // ISO date string - endTime: t.string -}) - -// Define an API route with query parameters -const ListTransactions = httpRequest({ - method: 'GET', - query: t.intersection([ - Pagination, - TimeRange, - t.partial({ - status: t.union([ - t.literal('pending'), - t.literal('completed'), - t.literal('failed') - ]) - }) - ]) +// Define a request with query parameters and body +const SearchRequest = httpRequest({ + query: t.type({ + limit: t.number, + offset: t.number + }), + body: t.type({ + searchTerm: t.string, + filters: t.array(t.string) + }) }) -// Usage example -const validQuery = { - limit: 20, - offset: 0, - startTime: '2024-01-01T00:00:00Z', - endTime: '2024-01-31T23:59:59Z', - status: 'pending' +// Validate complete request +const validRequest = { + query: { limit: 10, offset: 0 }, + body: { + searchTerm: "api", + filters: ["active", "published"] + } } -ListTransactions.request.decode({ query: validQuery }) -// Success: Validates query parameters +SearchRequest.request.decode(validRequest) +// Success: { query: { limit: 10, offset: 0 }, body: { searchTerm: "api", filters: ["active", "published"] } } ``` -## Complex Request Bodies +## Edge Cases and Complex Types ```typescript import { Method } from '@api-ts/io-ts-http' -// Define nested structures -const Address = t.type({ - street: t.string, - city: t.string, - country: t.string, - postalCode: t.string -}) - -const CustomerInfo = t.intersection([ - t.type({ - id: t.string, - name: t.string, - email: t.string, - primaryAddress: Address - }), - t.partial({ - phone: t.string, - additionalAddresses: t.array(Address) - }) +// Union types for status codes +const StatusResponse = t.union([ + t.type({ status: t.literal(200), data: t.string }), + t.type({ status: t.literal(404), error: t.string }), + t.type({ status: t.literal(500), code: t.number, message: t.string }) ]) -// Create request with complex body -const UpdateCustomer = httpRequest({ - method: Method.enum.PUT, - params: t.type({ - customerId: t.string +// Request with optional fields and specific HTTP method +const ApiRequest = httpRequest({ + method: Method.enum.POST, + headers: t.partial({ + 'x-api-version': t.string, + 'x-client-id': t.string }), - headers: t.type({ - 'x-request-id': t.string + query: t.partial({ + debug: t.boolean }), - body: CustomerInfo + body: t.intersection([ + t.type({ required: t.string }), + t.partial({ optional: t.array(t.number) }) + ]) }) -// Example usage with validation errors -const invalidRequest = { - params: { customerId: '123' }, - headers: { 'x-request-id': 'req-456' }, - body: { - id: '123', - name: 'John Doe', - email: 'invalid-email', // Will fail validation - primaryAddress: { - street: '123 Main St', - city: 'Boston', - // country missing - will fail validation - postalCode: '02101' - } - } +// Examples of edge cases +const partialRequest = { + headers: { 'x-api-version': '2.0' }, + body: { required: 'test' } } -UpdateCustomer.request.decode(invalidRequest) -// Error: Validation failures for email format and missing country -``` - -## Response Handling - -```typescript -// Define response types with branded types for extra safety -const TransactionId = t.brand( - t.string, - (s): s is t.Branded => - /^tx-[a-f0-9]{24}$/.test(s), - 'TransactionId' -) - -const Transaction = t.type({ - id: TransactionId, - amount: t.number, - currency: t.string, - status: t.union([ - t.literal('pending'), - t.literal('completed'), - t.literal('failed') - ]), - metadata: t.record(t.string, t.unknown) -}) +ApiRequest.request.decode(partialRequest) +// Success: Accepts partial headers and query -// Define success and error responses -const GetTransaction = httpRequest({ - method: 'GET', - params: t.type({ - transactionId: TransactionId - }), - response: { - 200: Transaction, - 404: t.type({ - error: t.literal('TransactionNotFound'), - message: t.string - }), - 500: t.type({ - error: t.literal('InternalServerError'), - code: t.number, - message: t.string - }) - } -}) +const invalidMethod = { + method: 'GET', // Wrong method + body: { required: 'test' } +} +ApiRequest.request.decode(invalidMethod) +// Error: Invalid method, expected POST -// Example response handling -const response = { - status: 200, - body: { - id: 'tx-1234567890abcdef12345678', - amount: 100.50, - currency: 'USD', - status: 'completed', - metadata: { - source: 'web', - customer: 'cust_123' - } - } +const complexResponse = { + status: 500, + code: 5001, + message: 'Internal server error' } -GetTransaction.response[response.status].decode(response.body) -// Success: Validates response body against 200 status schema +StatusResponse.decode(complexResponse) +// Success: Matches 500 status union case ``` - -The library provides: -- Type-safe request and response validation -- Composable type definitions -- Branded types for custom validation -- Union types for handling multiple response types -- Partial types for optional fields -- Intersection types for combining schemas From 78c0b174bf7ff271bc13f0a69d3f6963b4fb0433 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:28:33 -0500 Subject: [PATCH 10/14] feat: Update json-parsing-examples.md with comprehensive api-ts usage patterns Co-authored-by: aider --- .../how-to-guides/json-parsing-examples.md | 199 +++++++++++++----- 1 file changed, 141 insertions(+), 58 deletions(-) diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md index 501d20c8..ee04a309 100644 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -1,83 +1,166 @@ -# How to Parse JSON Data +# JSON Parsing with api-ts -## Basic Request Validation +Learn how to define and validate API endpoints using api-ts codecs. + +## Common Patterns + +### Basic Route Definition ```typescript import * as t from 'io-ts' import { httpRequest } from '@api-ts/io-ts-http' -// Define a request with query parameters and body -const SearchRequest = httpRequest({ - query: t.type({ - limit: t.number, - offset: t.number +// Define common types +const PaginationQuery = t.type({ + page: t.number, + pageSize: t.number +}) + +const DateRangeQuery = t.type({ + startDate: t.string, // ISO date string + endDate: t.string +}) + +// Define a GET endpoint with query parameters +const ListTransactions = httpRequest({ + method: 'GET', + query: t.intersection([ + PaginationQuery, + DateRangeQuery, + t.partial({ + status: t.union([ + t.literal('PENDING'), + t.literal('COMPLETED'), + t.literal('FAILED') + ]) + }) + ]) +}) + +// Example usage +const validQuery = { + page: 1, + pageSize: 50, + startDate: '2024-01-01T00:00:00Z', + endDate: '2024-01-31T23:59:59Z', + status: 'PENDING' +} +ListTransactions.request.decode({ query: validQuery }) +``` + +### Request Body Validation + +```typescript +// Define reusable types +const MoneyAmount = t.type({ + amount: t.number, + currency: t.string +}) + +const TransactionDetails = t.intersection([ + t.type({ + id: t.string, + type: t.union([ + t.literal('DEPOSIT'), + t.literal('WITHDRAWAL') + ]), + amount: MoneyAmount }), - body: t.type({ - searchTerm: t.string, - filters: t.array(t.string) + t.partial({ + description: t.string, + metadata: t.record(t.string, t.unknown) }) +]) + +// POST endpoint with body validation +const CreateTransaction = httpRequest({ + method: 'POST', + headers: t.type({ + 'x-idempotency-key': t.string + }), + body: TransactionDetails }) -// Validate complete request -const validRequest = { - query: { limit: 10, offset: 0 }, - body: { - searchTerm: "api", - filters: ["active", "published"] +// Example usage +const validBody = { + id: 'tx_123', + type: 'DEPOSIT', + amount: { + amount: 100.50, + currency: 'USD' + }, + metadata: { + source: 'web', + user_agent: 'Mozilla/5.0' } } -SearchRequest.request.decode(validRequest) -// Success: { query: { limit: 10, offset: 0 }, body: { searchTerm: "api", filters: ["active", "published"] } } +CreateTransaction.request.decode({ + headers: { 'x-idempotency-key': 'key_123' }, + body: validBody +}) ``` -## Edge Cases and Complex Types +### Response Handling ```typescript -import { Method } from '@api-ts/io-ts-http' +// Define error responses +const ErrorResponse = t.type({ + code: t.string, + message: t.string, + details: t.array(t.string) +}) -// Union types for status codes -const StatusResponse = t.union([ - t.type({ status: t.literal(200), data: t.string }), - t.type({ status: t.literal(404), error: t.string }), - t.type({ status: t.literal(500), code: t.number, message: t.string }) +// Define success response +const TransactionResponse = t.intersection([ + TransactionDetails, + t.type({ + status: t.union([ + t.literal('PENDING'), + t.literal('COMPLETED'), + t.literal('FAILED') + ]), + createdAt: t.string, + updatedAt: t.string + }) ]) -// Request with optional fields and specific HTTP method -const ApiRequest = httpRequest({ - method: Method.enum.POST, - headers: t.partial({ - 'x-api-version': t.string, - 'x-client-id': t.string - }), - query: t.partial({ - debug: t.boolean +// Complete endpoint definition with responses +const GetTransaction = httpRequest({ + method: 'GET', + params: t.type({ + transactionId: t.string }), - body: t.intersection([ - t.type({ required: t.string }), - t.partial({ optional: t.array(t.number) }) - ]) + response: { + 200: TransactionResponse, + 404: ErrorResponse, + 500: ErrorResponse + } }) -// Examples of edge cases -const partialRequest = { - headers: { 'x-api-version': '2.0' }, - body: { required: 'test' } +// Example response handling +const response = { + status: 200, + body: { + id: 'tx_123', + type: 'DEPOSIT', + amount: { + amount: 100.50, + currency: 'USD' + }, + status: 'COMPLETED', + createdAt: '2024-01-15T10:30:00Z', + updatedAt: '2024-01-15T10:31:00Z' + } } -ApiRequest.request.decode(partialRequest) -// Success: Accepts partial headers and query +GetTransaction.response[response.status].decode(response.body) +``` -const invalidMethod = { - method: 'GET', // Wrong method - body: { required: 'test' } -} -ApiRequest.request.decode(invalidMethod) -// Error: Invalid method, expected POST +## Best Practices -const complexResponse = { - status: 500, - code: 5001, - message: 'Internal server error' -} -StatusResponse.decode(complexResponse) -// Success: Matches 500 status union case -``` +1. **Reusable Types**: Define common structures like pagination, date ranges, and money amounts as reusable types +2. **Required vs Optional**: Use `t.intersection` with `t.partial` to clearly separate required and optional fields +3. **Error Handling**: Define consistent error response structures across endpoints +4. **Idempotency**: Include idempotency keys in headers for state-changing operations +5. **Timestamps**: Use ISO date strings for timestamps +6. **Enums**: Use literal unions for enumerated values +7. **Metadata**: Allow flexible metadata using `t.record(t.string, t.unknown)` From e304a11a4a10875979fd3cb24c42868af0f8617c Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:31:46 -0500 Subject: [PATCH 11/14] docs: Add project context and file references to JSON parsing guide Co-authored-by: aider --- website/docs/how-to-guides/json-parsing-examples.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md index ee04a309..ae78cf6c 100644 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -1,10 +1,11 @@ # JSON Parsing with api-ts -Learn how to define and validate API endpoints using api-ts codecs. +Learn how to define and validate API endpoints using api-ts codecs. These examples are based on real-world usage patterns from the compliance-tx-monitoring project. ## Common Patterns ### Basic Route Definition +Similar patterns found in `src/routes/list-transactions.ts` and `src/routes/search-addresses.ts`: ```typescript import * as t from 'io-ts' @@ -49,6 +50,7 @@ ListTransactions.request.decode({ query: validQuery }) ``` ### Request Body Validation +Similar patterns found in `src/types/common.ts` and `src/routes/create-transaction.ts`: ```typescript // Define reusable types @@ -101,6 +103,7 @@ CreateTransaction.request.decode({ ``` ### Response Handling +Similar patterns found in `src/types/errors.ts` and `src/routes/get-transaction.ts`: ```typescript // Define error responses @@ -156,6 +159,7 @@ GetTransaction.response[response.status].decode(response.body) ``` ## Best Practices +Found throughout the codebase in `src/types/common.ts`, `src/routes/*`, and `src/middleware/*`: 1. **Reusable Types**: Define common structures like pagination, date ranges, and money amounts as reusable types 2. **Required vs Optional**: Use `t.intersection` with `t.partial` to clearly separate required and optional fields From 6b8d4e388df2a58cacadffe0a18927c0b27a26bc Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:39:48 -0500 Subject: [PATCH 12/14] feat: Add JSON string parsing example to documentation Co-authored-by: aider --- .../how-to-guides/json-parsing-examples.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md index ae78cf6c..27f5b697 100644 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -2,6 +2,37 @@ Learn how to define and validate API endpoints using api-ts codecs. These examples are based on real-world usage patterns from the compliance-tx-monitoring project. +## Parsing Raw JSON Strings + +The most basic use case is parsing a JSON string and validating its structure: + +```typescript +import * as t from 'io-ts' + +// Define the expected structure +const UserCodec = t.type({ + name: t.string, + age: t.number, + email: t.string +}) + +// Parse JSON string and validate structure +const jsonString = '{"name": "Alice", "age": 30, "email": "alice@example.com"}' + +// First parse the JSON string +const parsed = JSON.parse(jsonString) + +// Then validate the parsed data +const result = UserCodec.decode(parsed) +// Success: { name: "Alice", age: 30, email: "alice@example.com" } + +// Example with invalid data +const invalidJson = '{"name": "Bob", "age": "30"}' // age should be number +const invalidParsed = JSON.parse(invalidJson) +const invalidResult = UserCodec.decode(invalidParsed) +// Error: Invalid value "30" supplied to : { name: string, age: number, email: string }/age: number +``` + ## Common Patterns ### Basic Route Definition From 7972d5e53944dcf8995073eda5785ac89dec7324 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:42:04 -0500 Subject: [PATCH 13/14] refactor: Split JSON parsing content into separate documentation files Co-authored-by: aider --- .../how-to-guides/json-parsing-examples.md | 35 +----- .../how-to-guides/parsing-json-strings.md | 111 ++++++++++++++++++ 2 files changed, 113 insertions(+), 33 deletions(-) create mode 100644 website/docs/how-to-guides/parsing-json-strings.md diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md index 27f5b697..6304a7b2 100644 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ b/website/docs/how-to-guides/json-parsing-examples.md @@ -1,39 +1,8 @@ -# JSON Parsing with api-ts +# Common API Patterns with api-ts Learn how to define and validate API endpoints using api-ts codecs. These examples are based on real-world usage patterns from the compliance-tx-monitoring project. -## Parsing Raw JSON Strings - -The most basic use case is parsing a JSON string and validating its structure: - -```typescript -import * as t from 'io-ts' - -// Define the expected structure -const UserCodec = t.type({ - name: t.string, - age: t.number, - email: t.string -}) - -// Parse JSON string and validate structure -const jsonString = '{"name": "Alice", "age": 30, "email": "alice@example.com"}' - -// First parse the JSON string -const parsed = JSON.parse(jsonString) - -// Then validate the parsed data -const result = UserCodec.decode(parsed) -// Success: { name: "Alice", age: 30, email: "alice@example.com" } - -// Example with invalid data -const invalidJson = '{"name": "Bob", "age": "30"}' // age should be number -const invalidParsed = JSON.parse(invalidJson) -const invalidResult = UserCodec.decode(invalidParsed) -// Error: Invalid value "30" supplied to : { name: string, age: number, email: string }/age: number -``` - -## Common Patterns +## Route Definitions ### Basic Route Definition Similar patterns found in `src/routes/list-transactions.ts` and `src/routes/search-addresses.ts`: diff --git a/website/docs/how-to-guides/parsing-json-strings.md b/website/docs/how-to-guides/parsing-json-strings.md new file mode 100644 index 00000000..cb0f5876 --- /dev/null +++ b/website/docs/how-to-guides/parsing-json-strings.md @@ -0,0 +1,111 @@ +# Parsing JSON Strings with api-ts + +Learn how to parse and validate JSON strings using api-ts codecs. + +## Basic JSON String Parsing + +The most basic use case is parsing a JSON string and validating its structure: + +```typescript +import * as t from 'io-ts' + +// Define the expected structure +const UserCodec = t.type({ + name: t.string, + age: t.number, + email: t.string +}) + +// Parse JSON string and validate structure +const jsonString = '{"name": "Alice", "age": 30, "email": "alice@example.com"}' + +// First parse the JSON string +const parsed = JSON.parse(jsonString) + +// Then validate the parsed data +const result = UserCodec.decode(parsed) +// Success: { name: "Alice", age: 30, email: "alice@example.com" } + +// Example with invalid data +const invalidJson = '{"name": "Bob", "age": "30"}' // age should be number +const invalidParsed = JSON.parse(invalidJson) +const invalidResult = UserCodec.decode(invalidParsed) +// Error: Invalid value "30" supplied to : { name: string, age: number, email: string }/age: number +``` + +## Advanced Transformations + +Here's how to handle more complex JSON parsing scenarios: + +```typescript +import * as t from 'io-ts' +import { DateFromISOString } from 'io-ts-types' + +// Define a codec that handles nested structures and dates +const EventCodec = t.type({ + id: t.string, + timestamp: DateFromISOString, + data: t.type({ + title: t.string, + participants: t.array(t.type({ + id: t.string, + role: t.union([ + t.literal('organizer'), + t.literal('attendee') + ]) + })) + }) +}) + +// Example JSON string with nested structure +const jsonString = `{ + "id": "evt_123", + "timestamp": "2024-01-15T10:30:00Z", + "data": { + "title": "Team Meeting", + "participants": [ + {"id": "user_1", "role": "organizer"}, + {"id": "user_2", "role": "attendee"} + ] + } +}` + +// Parse and validate in one step +const result = EventCodec.decode(JSON.parse(jsonString)) +// Success: Parsed with proper Date object and validated structure +``` + +## Error Handling + +Here's how to handle parsing and validation errors gracefully: + +```typescript +import * as E from 'fp-ts/Either' +import { pipe } from 'fp-ts/function' +import { failure } from 'io-ts/lib/PathReporter' + +// Helper function to safely parse and validate JSON +function parseAndValidate(codec: t.Type, input: string): E.Either { + try { + const parsed = JSON.parse(input) + return pipe( + codec.decode(parsed), + E.mapLeft(errors => failure(errors)) + ) + } catch (e) { + return E.left(['Invalid JSON format']) + } +} + +// Usage example +const result = parseAndValidate(UserCodec, invalidJson) +// Left(['Invalid value "30" supplied to : { name: string, age: number, email: string }/age: number']) +``` + +## Best Practices + +1. **Separate Concerns**: Keep JSON parsing separate from business logic +2. **Type Safety**: Define explicit codecs for expected data structures +3. **Error Handling**: Always handle both JSON parsing and validation errors +4. **Transformations**: Use codec composition for complex transformations +5. **Validation**: Include detailed validation messages for debugging From 083c20d7ab32d96f30fc9b2908b9ed744951c5b1 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 11 Feb 2025 13:56:20 -0500 Subject: [PATCH 14/14] feat: Add JSON parsing examples with io-ts in TypeScript --- website/docs/how-to-guides/_category_.json | 4 +- .../how-to-guides/json-parsing-examples.md | 170 ------------------ .../how-to-guides/parsing-json-strings.md | 48 +---- 3 files changed, 5 insertions(+), 217 deletions(-) delete mode 100644 website/docs/how-to-guides/json-parsing-examples.md diff --git a/website/docs/how-to-guides/_category_.json b/website/docs/how-to-guides/_category_.json index 12e3146b..efc0295f 100644 --- a/website/docs/how-to-guides/_category_.json +++ b/website/docs/how-to-guides/_category_.json @@ -1,8 +1,8 @@ { - "label": "How-To Guides", + "label": "How - To Guides", "position": 3, "link": { "type": "generated-index", - "description": "Learn how to use the library with practical examples and guides" + "description": "5 minutes to practice the most important api-ts concepts." } } diff --git a/website/docs/how-to-guides/json-parsing-examples.md b/website/docs/how-to-guides/json-parsing-examples.md deleted file mode 100644 index 6304a7b2..00000000 --- a/website/docs/how-to-guides/json-parsing-examples.md +++ /dev/null @@ -1,170 +0,0 @@ -# Common API Patterns with api-ts - -Learn how to define and validate API endpoints using api-ts codecs. These examples are based on real-world usage patterns from the compliance-tx-monitoring project. - -## Route Definitions - -### Basic Route Definition -Similar patterns found in `src/routes/list-transactions.ts` and `src/routes/search-addresses.ts`: - -```typescript -import * as t from 'io-ts' -import { httpRequest } from '@api-ts/io-ts-http' - -// Define common types -const PaginationQuery = t.type({ - page: t.number, - pageSize: t.number -}) - -const DateRangeQuery = t.type({ - startDate: t.string, // ISO date string - endDate: t.string -}) - -// Define a GET endpoint with query parameters -const ListTransactions = httpRequest({ - method: 'GET', - query: t.intersection([ - PaginationQuery, - DateRangeQuery, - t.partial({ - status: t.union([ - t.literal('PENDING'), - t.literal('COMPLETED'), - t.literal('FAILED') - ]) - }) - ]) -}) - -// Example usage -const validQuery = { - page: 1, - pageSize: 50, - startDate: '2024-01-01T00:00:00Z', - endDate: '2024-01-31T23:59:59Z', - status: 'PENDING' -} -ListTransactions.request.decode({ query: validQuery }) -``` - -### Request Body Validation -Similar patterns found in `src/types/common.ts` and `src/routes/create-transaction.ts`: - -```typescript -// Define reusable types -const MoneyAmount = t.type({ - amount: t.number, - currency: t.string -}) - -const TransactionDetails = t.intersection([ - t.type({ - id: t.string, - type: t.union([ - t.literal('DEPOSIT'), - t.literal('WITHDRAWAL') - ]), - amount: MoneyAmount - }), - t.partial({ - description: t.string, - metadata: t.record(t.string, t.unknown) - }) -]) - -// POST endpoint with body validation -const CreateTransaction = httpRequest({ - method: 'POST', - headers: t.type({ - 'x-idempotency-key': t.string - }), - body: TransactionDetails -}) - -// Example usage -const validBody = { - id: 'tx_123', - type: 'DEPOSIT', - amount: { - amount: 100.50, - currency: 'USD' - }, - metadata: { - source: 'web', - user_agent: 'Mozilla/5.0' - } -} -CreateTransaction.request.decode({ - headers: { 'x-idempotency-key': 'key_123' }, - body: validBody -}) -``` - -### Response Handling -Similar patterns found in `src/types/errors.ts` and `src/routes/get-transaction.ts`: - -```typescript -// Define error responses -const ErrorResponse = t.type({ - code: t.string, - message: t.string, - details: t.array(t.string) -}) - -// Define success response -const TransactionResponse = t.intersection([ - TransactionDetails, - t.type({ - status: t.union([ - t.literal('PENDING'), - t.literal('COMPLETED'), - t.literal('FAILED') - ]), - createdAt: t.string, - updatedAt: t.string - }) -]) - -// Complete endpoint definition with responses -const GetTransaction = httpRequest({ - method: 'GET', - params: t.type({ - transactionId: t.string - }), - response: { - 200: TransactionResponse, - 404: ErrorResponse, - 500: ErrorResponse - } -}) - -// Example response handling -const response = { - status: 200, - body: { - id: 'tx_123', - type: 'DEPOSIT', - amount: { - amount: 100.50, - currency: 'USD' - }, - status: 'COMPLETED', - createdAt: '2024-01-15T10:30:00Z', - updatedAt: '2024-01-15T10:31:00Z' - } -} -GetTransaction.response[response.status].decode(response.body) -``` - -## Best Practices -Found throughout the codebase in `src/types/common.ts`, `src/routes/*`, and `src/middleware/*`: - -1. **Reusable Types**: Define common structures like pagination, date ranges, and money amounts as reusable types -2. **Required vs Optional**: Use `t.intersection` with `t.partial` to clearly separate required and optional fields -3. **Error Handling**: Define consistent error response structures across endpoints -4. **Idempotency**: Include idempotency keys in headers for state-changing operations -5. **Timestamps**: Use ISO date strings for timestamps -6. **Enums**: Use literal unions for enumerated values -7. **Metadata**: Allow flexible metadata using `t.record(t.string, t.unknown)` diff --git a/website/docs/how-to-guides/parsing-json-strings.md b/website/docs/how-to-guides/parsing-json-strings.md index cb0f5876..1f697ab7 100644 --- a/website/docs/how-to-guides/parsing-json-strings.md +++ b/website/docs/how-to-guides/parsing-json-strings.md @@ -1,11 +1,6 @@ -# Parsing JSON Strings with api-ts - -Learn how to parse and validate JSON strings using api-ts codecs. +# How To Parse JSON Strings ## Basic JSON String Parsing - -The most basic use case is parsing a JSON string and validating its structure: - ```typescript import * as t from 'io-ts' @@ -33,9 +28,7 @@ const invalidResult = UserCodec.decode(invalidParsed) // Error: Invalid value "30" supplied to : { name: string, age: number, email: string }/age: number ``` -## Advanced Transformations - -Here's how to handle more complex JSON parsing scenarios: +## Nested JSON String Parsing ```typescript import * as t from 'io-ts' @@ -73,39 +66,4 @@ const jsonString = `{ // Parse and validate in one step const result = EventCodec.decode(JSON.parse(jsonString)) // Success: Parsed with proper Date object and validated structure -``` - -## Error Handling - -Here's how to handle parsing and validation errors gracefully: - -```typescript -import * as E from 'fp-ts/Either' -import { pipe } from 'fp-ts/function' -import { failure } from 'io-ts/lib/PathReporter' - -// Helper function to safely parse and validate JSON -function parseAndValidate(codec: t.Type, input: string): E.Either { - try { - const parsed = JSON.parse(input) - return pipe( - codec.decode(parsed), - E.mapLeft(errors => failure(errors)) - ) - } catch (e) { - return E.left(['Invalid JSON format']) - } -} - -// Usage example -const result = parseAndValidate(UserCodec, invalidJson) -// Left(['Invalid value "30" supplied to : { name: string, age: number, email: string }/age: number']) -``` - -## Best Practices - -1. **Separate Concerns**: Keep JSON parsing separate from business logic -2. **Type Safety**: Define explicit codecs for expected data structures -3. **Error Handling**: Always handle both JSON parsing and validation errors -4. **Transformations**: Use codec composition for complex transformations -5. **Validation**: Include detailed validation messages for debugging +``` \ No newline at end of file