From 255d9d07c02e5dfd93d299c9e71b5efb1bbc4925 Mon Sep 17 00:00:00 2001 From: yasun Date: Wed, 3 Dec 2025 16:04:34 +0800 Subject: [PATCH] feat: standardize TypeSpec schema definitions with enums and validation enhancements --- main.tsp | 6 ++ models-core/cluster/example_cluster.tsp | 6 +- models-core/nodepool/example_nodepool.tsp | 6 +- models-gcp/cluster/example_cluster.tsp | 6 +- models-gcp/nodepool/example_nodepool.tsp | 6 +- models/clusters/model.tsp | 2 +- models/common/model.tsp | 19 +++++- models/nodepools/model.tsp | 2 +- models/statuses/example_adapter_status.tsp | 16 ++--- models/statuses/model.tsp | 14 +++- schemas/core/openapi.yaml | 75 ++++++++++++++-------- schemas/gcp/openapi.yaml | 75 ++++++++++++++-------- services/compatibility.tsp | 2 +- 13 files changed, 154 insertions(+), 81 deletions(-) diff --git a/main.tsp b/main.tsp index e15f25d..fa4f01f 100644 --- a/main.tsp +++ b/main.tsp @@ -23,3 +23,9 @@ using OpenAPI; @route("/api/hyperfleet/v1") namespace HyperFleet; +// Override BearerAuth to use lowercase "bearer" as required by kin-openapi +model BearerAuth { + type: AuthType.http; + scheme: "bearer"; +} + diff --git a/models-core/cluster/example_cluster.tsp b/models-core/cluster/example_cluster.tsp index 5ed519c..674a92d 100644 --- a/models-core/cluster/example_cluster.tsp +++ b/models-core/cluster/example_cluster.tsp @@ -12,14 +12,14 @@ const exampleCluster: Cluster = #{ updated_time: "2021-01-01T00:00:00Z", generation: 1, status: #{ - phase: "Ready", + phase: ResourcePhase.Ready, last_transition_time: "2021-01-01T00:00:00Z", observed_generation: 1, last_updated_time: "2021-01-01T00:00:00Z", conditions: #[ #{ type: "ValidationSuccessful", - status: "True", + status: ConditionStatus.True, reason: "All validations passed", message: "All 30 validation tests passed", observed_generation: 1, @@ -29,7 +29,7 @@ const exampleCluster: Cluster = #{ }, #{ type: "DNSSuccessful", - status: "True", + status: ConditionStatus.True, reason: "DNS configured", message: "DNS records created for custom.domain.com", observed_generation: 1, diff --git a/models-core/nodepool/example_nodepool.tsp b/models-core/nodepool/example_nodepool.tsp index f94df0a..2d08576 100644 --- a/models-core/nodepool/example_nodepool.tsp +++ b/models-core/nodepool/example_nodepool.tsp @@ -18,14 +18,14 @@ const exampleNodePool: NodePool = #{ href: "https://api.hyperfleet.com/v1/clusters/cluster-123", }, status: #{ - phase: "Ready", + phase: ResourcePhase.Ready, last_transition_time: "2021-01-01T10:00:00Z", observed_generation: 1, last_updated_time: "2021-01-01T10:02:00Z", conditions: #[ #{ type: "ValidationSuccessful", - status: "True", + status: ConditionStatus.True, reason: "All validations passed", message: "NodePool validation passed", observed_generation: 1, @@ -35,7 +35,7 @@ const exampleNodePool: NodePool = #{ }, #{ type: "NodePoolSuccessful", - status: "True", + status: ConditionStatus.True, reason: "NodePool provisioned successfully", message: "NodePool has been scaled to desired count", observed_generation: 1, diff --git a/models-gcp/cluster/example_cluster.tsp b/models-gcp/cluster/example_cluster.tsp index 88cab5c..a0e0116 100644 --- a/models-gcp/cluster/example_cluster.tsp +++ b/models-gcp/cluster/example_cluster.tsp @@ -34,14 +34,14 @@ const exampleCluster: Cluster = #{ updated_time: "2021-01-01T00:00:00Z", generation: 1, status: #{ - phase: "Ready", + phase: ResourcePhase.Ready, last_transition_time: "2021-01-01T00:00:00Z", observed_generation: 1, last_updated_time: "2021-01-01T00:00:00Z", conditions: #[ #{ type: "ValidationSuccessful", - status: "True", + status: ConditionStatus.True, reason: "All validations passed", message: "All 30 validation tests passed", observed_generation: 1, @@ -51,7 +51,7 @@ const exampleCluster: Cluster = #{ }, #{ type: "DNSSuccessful", - status: "True", + status: ConditionStatus.True, reason: "DNS configured", message: "DNS records created for custom.domain.com", observed_generation: 1, diff --git a/models-gcp/nodepool/example_nodepool.tsp b/models-gcp/nodepool/example_nodepool.tsp index 395c95b..3ca171e 100644 --- a/models-gcp/nodepool/example_nodepool.tsp +++ b/models-gcp/nodepool/example_nodepool.tsp @@ -40,14 +40,14 @@ const exampleNodePool: NodePool = #{ href: "https://api.hyperfleet.com/v1/clusters/cluster-123", }, status: #{ - phase: "Ready", + phase: ResourcePhase.Ready, last_transition_time: "2021-01-01T10:00:00Z", observed_generation: 1, last_updated_time: "2021-01-01T10:02:00Z", conditions: #[ #{ type: "ValidationSuccessful", - status: "True", + status: ConditionStatus.True, reason: "All validations passed", message: "NodePool validation passed", observed_generation: 1, @@ -57,7 +57,7 @@ const exampleNodePool: NodePool = #{ }, #{ type: "NodePoolSuccessful", - status: "True", + status: ConditionStatus.True, reason: "NodePool provisioned successfully", message: "NodePool has 3 nodes running", observed_generation: 1, diff --git a/models/clusters/model.tsp b/models/clusters/model.tsp index bfe60e2..b1edfdd 100644 --- a/models/clusters/model.tsp +++ b/models/clusters/model.tsp @@ -34,7 +34,7 @@ model ClusterStatus { * Updated when conditions are reported. * Note: status.phase provides aggregated view from all conditions. */ - phase: "NotReady" | "Ready" | "Failed"; + phase: ResourcePhase; /** * When cluster last transitioned (used by Sentinel for backoff) diff --git a/models/common/model.tsp b/models/common/model.tsp index e322753..c98fd1e 100644 --- a/models/common/model.tsp +++ b/models/common/model.tsp @@ -10,6 +10,15 @@ alias KindClusterStatus = "ClusterStatus"; alias KindNodePoolStatus = "NodePoolStatus"; alias KindNodePool = "NodePool"; +/** + * Phase of a resource (Cluster or NodePool) + */ +enum ResourcePhase { + NotReady, + Ready, + Failed, +} + model ID { /** Resource identifier */ id?: Identifier; @@ -47,6 +56,14 @@ model Error { code?: string; reason?: string; operation_id?: string; + + /** Field-level validation errors (optional) */ + details?: { + /** Field path that failed validation */ + field?: string; + /** Validation error message for this field */ + error?: string; + }[]; } model ErrorResponse { @@ -94,7 +111,7 @@ model QueryParams extends SearchParams { orderBy?: string = "created_time"; @query - order?: OrderDirection = OrderDirection.desc; + order?: OrderDirection; } model List { diff --git a/models/nodepools/model.tsp b/models/nodepools/model.tsp index e9856e2..23fd494 100644 --- a/models/nodepools/model.tsp +++ b/models/nodepools/model.tsp @@ -27,7 +27,7 @@ model NodePoolStatus { * Updated when conditions are reported. * Note: status.phase provides aggregated view from all conditions. */ - phase: "NotReady" | "Ready" | "Failed"; + phase: ResourcePhase; /** * Last generation processed diff --git a/models/statuses/example_adapter_status.tsp b/models/statuses/example_adapter_status.tsp index 4fd7c3d..d4e5e3c 100644 --- a/models/statuses/example_adapter_status.tsp +++ b/models/statuses/example_adapter_status.tsp @@ -7,21 +7,21 @@ const exampleAdapterStatus: AdapterStatus = (#{ conditions: #[ #{ type: "Available", - status: "True", + status: ConditionStatus.True, reason: "All validations passed", message: "All 30 validation tests passed", last_transition_time: "2021-01-01T10:00:00Z", }, #{ type: "Applied", - status: "True", + status: ConditionStatus.True, reason: "Validation job applied", message: "Validation job applied successfully", last_transition_time: "2021-01-01T10:00:00Z", }, #{ type: "Health", - status: "True", + status: ConditionStatus.True, reason: "All adapter operations completed successfully", message: "All adapter runtime operations completed successfully", last_transition_time: "2021-01-01T10:00:00Z", @@ -54,19 +54,19 @@ const exampleAdapterStatusCreateRequest: AdapterStatusCreateRequest = (#{ conditions: #[ #{ type: "Available", - status: "True", + status: ConditionStatus.True, reason: "All validations passed", message: "All 30 validation tests passed", }, #{ type: "Applied", - status: "True", + status: ConditionStatus.True, reason: "Validation job applied", message: "Validation job applied successfully", }, #{ type: "Health", - status: "True", + status: ConditionStatus.True, reason: "All adapter operations completed successfully", message: "All adapter runtime operations completed successfully", } @@ -101,7 +101,7 @@ const exampleAdapterStatusList: AdapterStatusList = (#{ conditions: #[ #{ type: "Available", - status: "True", + status: ConditionStatus.True, reason: "All validations passed", message: "All 30 validation tests passed", last_transition_time: "2021-01-01T10:00:00Z", @@ -120,7 +120,7 @@ const exampleAdapterStatusList: AdapterStatusList = (#{ conditions: #[ #{ type: "Available", - status: "True", + status: ConditionStatus.True, reason: "DNS configured", message: "DNS records created", last_transition_time: "2021-01-01T10:01:00Z", diff --git a/models/statuses/model.tsp b/models/statuses/model.tsp index 8c55482..e115022 100644 --- a/models/statuses/model.tsp +++ b/models/statuses/model.tsp @@ -1,6 +1,15 @@ import "../common/model.tsp"; import "./example_adapter_status.tsp"; +/** + * Status value for conditions + */ +enum ConditionStatus { + True: "True", + False: "False", + Unknown, +} + /** * Base condition fields shared by all condition types */ @@ -13,7 +22,7 @@ model ConditionBase { /** * Condition status */ - status: "True" | "False" | "Unknown"; + status: ConditionStatus; /** * Machine-readable reason code @@ -72,7 +81,7 @@ model ResourceCondition extends ConditionBase { */ model ConditionRequest { type: string; - status: "True" | "False" | "Unknown"; + status: ConditionStatus; reason?: string; message?: string; } @@ -153,6 +162,5 @@ model AdapterStatusCreateRequest extends AdapterStatusBase { */ @example(exampleAdapterStatusList) model AdapterStatusList extends List { - kind: "AdapterStatusList"; items: AdapterStatus[]; } diff --git a/schemas/core/openapi.yaml b/schemas/core/openapi.yaml index 5d32c17..4cfd21f 100644 --- a/schemas/core/openapi.yaml +++ b/schemas/core/openapi.yaml @@ -366,7 +366,6 @@ components: required: false schema: $ref: '#/components/schemas/OrderDirection' - default: desc explode: false QueryParams.orderBy: name: orderBy @@ -572,13 +571,8 @@ components: AdapterStatusList: type: object required: - - kind - items properties: - kind: - type: string - enum: - - AdapterStatusList items: type: array items: @@ -615,6 +609,20 @@ components: last_transition_time: '2021-01-01T10:01:00Z' created_time: '2021-01-01T10:01:00Z' last_report_time: '2021-01-01T10:01:30Z' + BearerAuth: + type: object + required: + - type + - scheme + properties: + type: + type: string + enum: + - http + scheme: + type: string + enum: + - bearer Cluster: type: object required: @@ -745,11 +753,8 @@ components: - conditions properties: phase: - type: string - enum: - - NotReady - - Ready - - Failed + allOf: + - $ref: '#/components/schemas/ResourcePhase' description: |- Current cluster phase (native database column). Updated when conditions are reported. @@ -798,11 +803,8 @@ components: type: string description: Condition type status: - type: string - enum: - - 'True' - - 'False' - - Unknown + allOf: + - $ref: '#/components/schemas/ConditionStatus' description: Condition status reason: type: string @@ -826,11 +828,7 @@ components: type: type: string status: - type: string - enum: - - 'True' - - 'False' - - Unknown + $ref: '#/components/schemas/ConditionStatus' reason: type: string message: @@ -838,6 +836,13 @@ components: description: |- Condition data for create/update requests (from adapters) observed_generation and observed_time are now at AdapterStatusCreateRequest level + ConditionStatus: + type: string + enum: + - 'True' + - 'False' + - Unknown + description: Status value for conditions Error: type: object properties: @@ -855,6 +860,18 @@ components: type: string operation_id: type: string + details: + type: array + items: + type: object + properties: + field: + type: string + description: Field path that failed validation + error: + type: string + description: Validation error message for this field + description: Field-level validation errors (optional) List: type: object required: @@ -1090,11 +1107,8 @@ components: - conditions properties: phase: - type: string - enum: - - NotReady - - Ready - - Failed + allOf: + - $ref: '#/components/schemas/ResourcePhase' description: |- Current NodePool phase (native database column). Updated when conditions are reported. @@ -1173,10 +1187,17 @@ components: Condition in Cluster/NodePool status Used for semantic condition types: "ValidationSuccessful", "DNSSuccessful", "NodePoolSuccessful", etc. Includes observed_generation and last_updated_time to track adapter-specific state + ResourcePhase: + type: string + enum: + - NotReady + - Ready + - Failed + description: Phase of a resource (Cluster or NodePool) securitySchemes: BearerAuth: type: http - scheme: Bearer + scheme: bearer servers: - url: http://localhost:8000 description: Development diff --git a/schemas/gcp/openapi.yaml b/schemas/gcp/openapi.yaml index 93353d5..c1c97d5 100644 --- a/schemas/gcp/openapi.yaml +++ b/schemas/gcp/openapi.yaml @@ -366,7 +366,6 @@ components: required: false schema: $ref: '#/components/schemas/OrderDirection' - default: desc explode: false QueryParams.orderBy: name: orderBy @@ -583,13 +582,8 @@ components: AdapterStatusList: type: object required: - - kind - items properties: - kind: - type: string - enum: - - AdapterStatusList items: type: array items: @@ -649,6 +643,20 @@ components: type: string scaleUpDelay: type: string + BearerAuth: + type: object + required: + - type + - scheme + properties: + type: + type: string + enum: + - http + scheme: + type: string + enum: + - bearer Cluster: type: object required: @@ -818,11 +826,8 @@ components: - conditions properties: phase: - type: string - enum: - - NotReady - - Ready - - Failed + allOf: + - $ref: '#/components/schemas/ResourcePhase' description: |- Current cluster phase (native database column). Updated when conditions are reported. @@ -871,11 +876,8 @@ components: type: string description: Condition type status: - type: string - enum: - - 'True' - - 'False' - - Unknown + allOf: + - $ref: '#/components/schemas/ConditionStatus' description: Condition status reason: type: string @@ -899,11 +901,7 @@ components: type: type: string status: - type: string - enum: - - 'True' - - 'False' - - Unknown + $ref: '#/components/schemas/ConditionStatus' reason: type: string message: @@ -911,6 +909,13 @@ components: description: |- Condition data for create/update requests (from adapters) observed_generation and observed_time are now at AdapterStatusCreateRequest level + ConditionStatus: + type: string + enum: + - 'True' + - 'False' + - Unknown + description: Status value for conditions DNSSpec: type: object properties: @@ -933,6 +938,18 @@ components: type: string operation_id: type: string + details: + type: array + items: + type: object + properties: + field: + type: string + description: Field path that failed validation + error: + type: string + description: Validation error message for this field + description: Field-level validation errors (optional) GCPClusterSpec: type: object required: @@ -1295,11 +1312,8 @@ components: - conditions properties: phase: - type: string - enum: - - NotReady - - Ready - - Failed + allOf: + - $ref: '#/components/schemas/ResourcePhase' description: |- Current NodePool phase (native database column). Updated when conditions are reported. @@ -1397,6 +1411,13 @@ components: Condition in Cluster/NodePool status Used for semantic condition types: "ValidationSuccessful", "DNSSuccessful", "NodePoolSuccessful", etc. Includes observed_generation and last_updated_time to track adapter-specific state + ResourcePhase: + type: string + enum: + - NotReady + - Ready + - Failed + description: Phase of a resource (Cluster or NodePool) TaintSpec: type: object required: @@ -1416,7 +1437,7 @@ components: securitySchemes: BearerAuth: type: http - scheme: Bearer + scheme: bearer servers: - url: http://localhost:8000 description: Development diff --git a/services/compatibility.tsp b/services/compatibility.tsp index b2e51a6..2303044 100644 --- a/services/compatibility.tsp +++ b/services/compatibility.tsp @@ -17,6 +17,6 @@ interface Compatibility { @route("/compatibility") @get @operationId("getCompatibility") - @useAuth(BearerAuth) + @useAuth(HyperFleet.BearerAuth) getNodePools(...QueryParams): Body; }