-
Notifications
You must be signed in to change notification settings - Fork 16
Open
Description
Is your feature request related to a problem?
Many data fetches are not handling errors, nor validating data types.
Describe the solution you'd like
This issue identifies areas where Zod schemas can be used to validate:
- API endpoint inputs (query parameters, request bodies)
- API endpoint return values (response data)
- External API responses (third-party API data)
Current State
- Zod is already installed (
zod@^4.1.12in package.json) - Zod is currently used in
pages/api/usage.tsxto validate external API responses (DayDataSchema) - Basic address validation exists via
isValidAddress()helper, but could be enhanced with Zod
1. API Endpoint Input Validation
1.1 Address-based Endpoints
These endpoints accept an address query parameter that should be validated:
/api/account-balance/[address].tsx
- Input:
addressquery param - Current validation:
isValidAddress(address)- basic string check - Zod opportunity: Create
AddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/)for stricter validation
/api/ens-data/[address].tsx
- Input:
addressquery param - Current validation:
isValidAddress(address)+ blacklist check - Zod opportunity: Same as above, plus validate blacklist separately
/api/score/[address].tsx
- Input:
addressquery param - Current validation:
isValidAddress(address) - Zod opportunity: Address validation schema
/api/pending-stake/[address].tsx
- Input:
addressquery param - Current validation:
isValidAddress(address) - Zod opportunity: Address validation schema
/api/l1-delegator/[address].tsx
- Input:
addressquery param - Current validation:
isValidAddress(address) - Zod opportunity: Address validation schema
1.2 Treasury/Proposal Endpoints
/api/treasury/proposal/[proposalId]/state.tsx
- Input:
proposalIdquery param (string) - Current validation: Basic existence check
if (!proposalId) - Zod opportunity:
const ProposalIdSchema = z.string().regex(/^\d+$/) // numeric string
/api/treasury/proposal/[proposalId]/votes/[address].tsx
- Input:
proposalIdquery paramaddressquery param
- Current validation: Basic checks
- Zod opportunity: Combined schema for both params
/api/treasury/votes/[address]/index.tsx
- Input:
addressquery param - Current validation:
isValidAddress(address) - Zod opportunity: Address validation schema
/api/treasury/votes/[address]/registered.tsx
- Input:
addressquery param - Current validation:
isValidAddress(address) - Zod opportunity: Address validation schema
1.3 POST Endpoints with Request Bodies
/api/upload-ipfs.tsx
- Input:
req.body(JSON object) - Current validation: None - directly passes
req.bodyto external API - Zod opportunity:
const IpfsUploadSchema = z.object({ // Define expected structure of IPFS upload data // This depends on what data is actually being uploaded })
/api/generateProof.tsx
- Input:
req.bodywith{ account, delegate, stake, fees } - Current validation: None - directly destructures from
req.body - Zod opportunity:
const GenerateProofSchema = z.object({ account: z.string().regex(/^0x[a-fA-F0-9]{40}$/), delegate: z.string().regex(/^0x[a-fA-F0-9]{40}$/), stake: z.string(), // or z.bigint() if needed fees: z.string(), // or z.bigint() if needed })
1.4 Query Parameters with Optional Values
/api/pipelines/index.tsx
- Input: Optional
regionquery param - Current validation: None
- Zod opportunity:
const PipelinesQuerySchema = z.object({ region: z.string().optional(), })
/api/score/index.tsx
- Input: Optional
pipelineandmodelquery params - Current validation: None
- Zod opportunity:
const ScoreQuerySchema = z.object({ pipeline: z.string().optional(), model: z.string().optional(), })
2. API Endpoint Return Value Validation
All API endpoints return typed data, but there's no runtime validation. Adding Zod schemas would catch contract/API changes early.
2.1 Account/Balance Endpoints
/api/account-balance/[address].tsx
- Return type:
AccountBalance - Zod opportunity:
const AccountBalanceSchema = z.object({ balance: z.string(), allowance: z.string(), })
/api/pending-stake/[address].tsx
- Return type:
PendingFeesAndStake - Zod opportunity:
const PendingFeesAndStakeSchema = z.object({ pendingStake: z.string(), pendingFees: z.string(), })
/api/l1-delegator/[address].tsx
- Return type:
L1Delegator - Zod opportunity:
const UnbondingLockSchema = z.object({ id: z.number(), amount: z.string(), withdrawRound: z.string(), }) const L1DelegatorSchema = z.object({ delegateAddress: z.string(), pendingStake: z.string(), pendingFees: z.string(), transcoderStatus: z.enum(["not-registered", "registered"]), unbondingLocks: z.array(UnbondingLockSchema), activeLocks: z.array(UnbondingLockSchema), })
2.2 ENS Endpoints
/api/ens-data/[address].tsx
- Return type:
EnsIdentity - Zod opportunity:
const EnsIdentitySchema = z.object({ id: z.string(), idShort: z.string(), avatar: z.string().nullable().optional(), name: z.string().nullable().optional(), url: z.string().nullable().optional(), twitter: z.string().nullable().optional(), github: z.string().nullable().optional(), description: z.string().nullable().optional(), })
2.3 Round/Protocol Endpoints
/api/current-round.tsx
- Return type:
CurrentRoundInfo - Zod opportunity:
const CurrentRoundInfoSchema = z.object({ id: z.number(), startBlock: z.number(), initialized: z.boolean(), currentL1Block: z.number(), currentL2Block: z.number(), })
2.4 Performance/Score Endpoints
/api/score/[address].tsx
- Return type:
PerformanceMetrics - Zod opportunity:
const RegionalValuesSchema = z.record(z.string(), z.number()) const ScoreSchema = z.object({ value: z.number(), region: z.string(), model: z.string(), pipeline: z.string(), orchestrator: z.string(), }) const PerformanceMetricsSchema = z.object({ successRates: RegionalValuesSchema, roundTripScores: RegionalValuesSchema, scores: RegionalValuesSchema, pricePerPixel: z.number(), topAIScore: ScoreSchema, })
/api/score/index.tsx
- Return type:
AllPerformanceMetrics - Zod opportunity:
const AllPerformanceMetricsSchema = z.record( z.string(), PerformanceMetricsSchema )
2.5 Treasury Endpoints
/api/treasury/proposal/[proposalId]/state.tsx
- Return type:
ProposalState - Zod opportunity:
const ProposalStateSchema = z.object({ id: z.string(), state: z.enum([ "Pending", "Active", "Canceled", "Defeated", "Succeeded", "Queued", "Expired", "Executed", "Unknown", ]), quota: z.string(), quorum: z.string(), totalVoteSupply: z.string(), votes: z.object({ against: z.string(), for: z.string(), abstain: z.string(), }), })
/api/treasury/votes/[address]/index.tsx
- Return type:
VotingPower - Zod opportunity:
const VotingPowerSchema = z.object({ proposalThreshold: z.string(), self: z.object({ address: z.string().regex(/^0x[a-fA-F0-9]{40}$/), votes: z.string(), }), delegate: z.object({ address: z.string().regex(/^0x[a-fA-F0-9]{40}$/), votes: z.string(), }).optional(), })
/api/treasury/votes/[address]/registered.tsx
- Return type:
RegisteredToVote - Zod opportunity:
const RegisteredToVoteSchema = z.object({ registered: z.boolean(), delegate: z.object({ address: z.string().regex(/^0x[a-fA-F0-9]{40}$/), registered: z.boolean(), }), })
/api/treasury/proposal/[proposalId]/votes/[address].tsx
- Return type:
ProposalVotingPower - Zod opportunity:
const ProposalVotingPowerSchema = z.object({ self: z.object({ address: z.string().regex(/^0x[a-fA-F0-9]{40}$/), votes: z.string(), hasVoted: z.boolean(), }), delegate: z.object({ address: z.string().regex(/^0x[a-fA-F0-9]{40}$/), votes: z.string(), hasVoted: z.boolean(), }).optional(), })
2.6 Pipeline/Region Endpoints
/api/pipelines/index.tsx
- Return type:
AvailablePipelines - Zod opportunity:
const PipelineSchema = z.object({ id: z.string(), models: z.array(z.string()), regions: z.array(z.string()), }) const AvailablePipelinesSchema = z.object({ pipelines: z.array(PipelineSchema), })
/api/regions/index.ts
- Return type:
Regions - Zod opportunity:
const RegionSchema = z.object({ id: z.string(), name: z.string(), type: z.enum(["transcoding", "ai"]), }) const RegionsSchema = z.object({ regions: z.array(RegionSchema), })
2.7 Usage/Chart Endpoints
/api/usage.tsx
- Return type:
HomeChartData - Current validation: Already uses
DayDataSchemafor external API response - Zod opportunity: Create schema for the full
HomeChartDatareturn type
/api/upload-ipfs.tsx
- Return type:
AddIpfs - Zod opportunity:
const AddIpfsSchema = z.object({ hash: z.string(), })
3. External API Response Validation
These endpoints fetch data from external APIs and should validate responses before using them.
3.1 Metrics/AI Server Responses
/api/score/[address].tsx
- External APIs:
NEXT_PUBLIC_AI_METRICS_SERVER_URL/api/top_ai_score→ScoreResponseNEXT_PUBLIC_METRICS_SERVER_URL/api/aggregated_stats→MetricsResponse- Pricing URL →
PriceResponse
- Current validation: None - directly uses
await response.json() - Zod opportunity:
const ScoreResponseSchema = z.object({ value: z.number(), region: z.string(), model: z.string(), pipeline: z.string(), orchestrator: z.string(), }) const MetricSchema = z.object({ success_rate: z.number(), round_trip_score: z.number(), score: z.number(), }) const MetricsResponseSchema = z.record( z.string(), z.record(z.string(), MetricSchema).optional() ) const PriceResponseSchema = z.array(z.object({ Address: z.string(), ServiceURI: z.string(), LastRewardRound: z.number(), RewardCut: z.number(), FeeShare: z.number(), DelegatedStake: z.string(), ActivationRound: z.number(), DeactivationRound: z.string(), Active: z.boolean(), Status: z.string(), PricePerPixel: z.number(), UpdatedAt: z.number(), }))
/api/score/index.tsx
- External APIs: Same as above
- Current validation: None
- Zod opportunity: Same schemas as above
/api/pipelines/index.tsx
- External API:
NEXT_PUBLIC_AI_METRICS_SERVER_URL/api/pipelines - Current validation: None - directly uses
await response.json() - Zod opportunity: Use
AvailablePipelinesSchema(defined in section 2.6)
/api/regions/index.ts
- External APIs:
NEXT_PUBLIC_METRICS_SERVER_URL/api/regionsNEXT_PUBLIC_AI_METRICS_SERVER_URL/api/regions
- Current validation: None
- Zod opportunity: Use
RegionsSchema(defined in section 2.6)
3.2 Livepeer.com API
/api/usage.tsx
- External API:
https://livepeer.com/data/usage/query/total - Current validation: ✅ Already uses
DayDataSchemawithsafeParse() - Status: Good example of proper validation
3.3 Pinata IPFS API
/api/upload-ipfs.tsx
- External API:
https://api.pinata.cloud/pinning/pinJSONToIPFS - Current validation: None - directly uses
await fetchResult.json() - Zod opportunity:
const PinataResponseSchema = z.object({ IpfsHash: z.string(), // Add other fields if Pinata returns more })
3.4 ENS Provider Responses
lib/api/ens.ts → getEnsForAddress()
- External API: Ethereum provider ENS lookups
- Current validation: None - directly uses provider responses
- Zod opportunity: Validate ENS resolver text records and avatar URLs
4. SSR/Client-Side API Calls
4.1 Server-Side Rendering
lib/api/ssr.ts → getEnsIdentity()
- Internal API call:
/api/ens-data/${address} - Current validation: None - directly uses
await response.json() - Zod opportunity: Use
EnsIdentitySchemato validate the response
5. Recommended Implementation Strategy
Phase 1: Shared Schemas
- Create
lib/api/schemas/directory - Create shared schemas for common types:
address.ts- Address validationcommon.ts- Common types (strings, numbers, etc.)ens.ts- ENS-related schemastreasury.ts- Treasury/proposal schemasperformance.ts- Performance metrics schemas
Phase 2: Input Validation
- Add input validation to all endpoints with query params
- Add request body validation to POST endpoints
- Return proper 400 errors with validation details
Phase 3: Output Validation
- Add return value validation to all endpoints
- Log validation errors (don't fail in production, but log for monitoring)
- Consider failing in development mode to catch issues early
Phase 4: External API Validation
- Validate all external API responses
- Use
safeParse()to handle validation errors gracefully - Add retry logic or fallback values where appropriate
Example Implementation Pattern
// lib/api/schemas/address.ts
import { z } from "zod";
export const AddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/);
// lib/api/schemas/account-balance.ts
import { z } from "zod";
export const AccountBalanceSchema = z.object({
balance: z.string(),
allowance: z.string(),
});
// pages/api/account-balance/[address].tsx
import { AddressSchema } from "@lib/api/schemas/address";
import { AccountBalanceSchema } from "@lib/api/schemas/account-balance";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
// Validate input
const addressResult = AddressSchema.safeParse(req.query.address);
if (!addressResult.success) {
return res.status(400).json({ error: "Invalid address", details: addressResult.error });
}
// ... fetch data ...
// Validate output
const result = AccountBalanceSchema.safeParse(accountBalance);
if (!result.success) {
console.error("Account balance validation failed:", result.error);
// In production, might still return the data, but log the error
// In development, could return 500 to catch issues early
}
return res.status(200).json(accountBalance);
};Summary
Total Opportunities:
- Input Validation: ~15 endpoints need query param/body validation
- Output Validation: ~20 endpoints need return value validation
- External API Validation: ~8 external API calls need response validation
Priority:
- High: External API responses (can cause runtime errors)
- Medium: POST request bodies (security/data integrity)
- Medium: Return value validation (catch contract changes)
- Low: Query parameter validation (basic checks exist)
Estimated Impact:
- Better error messages for invalid inputs
- Early detection of API contract changes
- Protection against malformed external API responses
- Improved type safety at runtime
Describe alternatives you've considered
No response
Additional context
No response
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
Status
Backlog